feat: add info popup with epg info for active channel

This commit closes #51
This commit is contained in:
4gray committed 2021-07-08 22:37:31 +02:00
1 parent 79e37973b9
commit 0eecfd1163
9 files changed
+233 -1

No files matched your search

@@ -73,6 +73,8 @@ export class HtmlVideoPlayerComponent implements OnChanges, OnDestroy {
* Destroy hls instance on component destroy * Destroy hls instance on component destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.hls.destroy(); if (this.hls) {
this.hls.destroy();
}
} }
} }
@@ -0,0 +1,36 @@
<div
class="channel-overlay"
fxLayout="row"
*ngIf="channel && epgProgram"
[class.visible]="isVisible"
[class.hidden]="!isVisible"
>
<div fxFlex="100px" fxLayoutAlign="center start" class="channel-logo">
<img
[src]="epgProgram?.icon[0] || channel?.tvg?.logo"
width="68"
onerror="this.style.display='none'"
/>
</div>
<div fxFlex>
<div class="program-name">
{{ epgProgram?.title[0]?.value }} | {{ channel.name }}
</div>
<div class="program-description">
{{ epgProgram?.desc[0]?.value }}
</div>
<div class="program-progress">
<div
class="program-finished"
[ngStyle]="{
width: (finishedDuration * 100) / generalDuration + '%'
}"
></div>
<div class="duration">
{{ start | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }}
-
{{ stop | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }}
</div>
</div>
</div>
</div>
@@ -0,0 +1,73 @@
.channel-overlay {
position: absolute;
bottom: 35px;
width: 100%;
background: #2c2b2fdc;
min-height: 130px;
max-height: 165px;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
backdrop-filter: blur(10px);
}
.program-name,
.program-description {
margin: 20px 0;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-logo {
margin-top: 20px;
}
.program-progress {
width: 96%;
background: #ccc;
height: 20px;
margin-bottom: 8px;
}
.program-name {
width: 96%;
max-height: 20px;
-webkit-line-clamp: 1;
}
.program-description {
width: 96%;
max-height: 55px;
-webkit-line-clamp: 3;
}
.program-finished {
background: #717171;
height: 20px;
font-size: 0.8em;
padding-left: 10px;
color: #000;
line-height: 20px;
}
.duration {
position: relative;
top: -18px;
font-size: 0.8em;
color: #000;
left: 10px;
}
.visible {
visibility: visible;
opacity: 1;
}
.hidden {
visibility: hidden;
opacity: 0;
transition: visibility 0s 2s, opacity 2s linear;
}
@@ -0,0 +1,29 @@
import { MockPipe } from 'ng-mocks';
import { MomentDatePipe } from './../../../shared/pipes/moment-date.pipe';
/* tslint:disable:no-unused-variable */
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { InfoOverlayComponent } from './info-overlay.component';
describe('InfoOverlayComponent', () => {
let component: InfoOverlayComponent;
let fixture: ComponentFixture<InfoOverlayComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [InfoOverlayComponent, MockPipe(MomentDatePipe)],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(InfoOverlayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,76 @@
import { Channel } from './../../../state/channel.model';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { EpgProgram } from '../../models/epg-program.model';
import * as moment from 'moment';
@Component({
selector: 'app-info-overlay',
templateUrl: './info-overlay.component.html',
styleUrls: ['./info-overlay.component.scss'],
})
export class InfoOverlayComponent implements OnChanges {
/** Active channel */
@Input() channel: Channel | undefined;
/** Current EPG program */
@Input() epgProgram: EpgProgram | undefined;
/** Visibility flag of the overlay popup */
isVisible = false;
/** Program duration */
generalDuration!: number;
/** Finished duration */
finishedDuration!: number;
/** Program start time */
start: moment.Moment;
/** Program end time */
stop: moment.Moment;
/** Timeout for the overlay visibility */
currentTimeout;
/**
* Calculates the necessary information for the visualization in the overview popup
* @param changes component input changes
*/
ngOnChanges(changes: SimpleChanges): void {
if (changes.channel && !changes.channel.firstChange) {
clearTimeout(this.currentTimeout);
this.isVisible = true;
this.currentTimeout = setTimeout(() => {
this.isVisible = false;
}, 4000);
}
if (
changes.epgProgram &&
changes.epgProgram.currentValue &&
!changes.epgProgram.firstChange
) {
const { stop, start } = changes.epgProgram.currentValue;
this.setProgramDuration(start, stop);
}
}
/**
* Calculates and sets the duration of the program for the progress bar visualization
* @param start program start time
* @param stop program stop time
*/
setProgramDuration(start: number, stop: number): void {
this.stop = moment(stop, 'YYYYMMDDHHmm ZZ');
this.start = moment(start, 'YYYYMMDDHHmm ZZ');
const timeNow = moment(Date.now());
this.generalDuration = moment
.duration(this.stop.diff(this.start))
.asMilliseconds();
this.finishedDuration = moment
.duration(this.stop.diff(timeNow))
.asMilliseconds();
}
}
@@ -68,6 +68,12 @@
[channel]="activeChannel$ | async" [channel]="activeChannel$ | async"
> >
</app-html-video-player> </app-html-video-player>
<!-- channel overlay -->
<app-info-overlay
[channel]="activeChannel$ | async"
[epgProgram]="epgProgram$ | async"
></app-info-overlay>
</mat-drawer-content> </mat-drawer-content>
<!-- right sidebar content --> <!-- right sidebar content -->
<mat-drawer <mat-drawer
@@ -1,3 +1,4 @@
import { InfoOverlayComponent } from './../info-overlay/info-overlay.component';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
@@ -35,6 +36,7 @@ describe('VideoPlayerComponent', () => {
MockComponent(VjsPlayerComponent), MockComponent(VjsPlayerComponent),
MockComponent(VideoPlayerComponent), MockComponent(VideoPlayerComponent),
MockComponent(ChannelListContainerComponent), MockComponent(ChannelListContainerComponent),
MockComponent(InfoOverlayComponent),
VideoPlayerComponent, VideoPlayerComponent,
MockPipe(TranslatePipe), MockPipe(TranslatePipe),
], ],
@@ -1,3 +1,4 @@
import { EpgProgram } from './../../models/epg-program.model';
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { ChannelQuery, Channel, ChannelStore } from '../../../state'; import { ChannelQuery, Channel, ChannelStore } from '../../../state';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -35,6 +36,11 @@ export class VideoPlayerComponent implements OnInit {
(store) => store.epgAvailable (store) => store.epgAvailable
); );
/** Current epg program */
epgProgram$: Observable<EpgProgram> = this.channelQuery.select(
(store) => store.currentEpgProgram
);
/** Favorites list */ /** Favorites list */
favorites$: Observable<string[]> = this.channelQuery.select( favorites$: Observable<string[]> = this.channelQuery.select(
(store) => store.favorites (store) => store.favorites
+2
View File
@@ -9,6 +9,7 @@ import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { EpgListComponent } from './components/epg-list/epg-list.component'; import { EpgListComponent } from './components/epg-list/epg-list.component';
import { EpgItemDescriptionComponent } from './components/epg-list/epg-item-description/epg-item-description.component'; import { EpgItemDescriptionComponent } from './components/epg-list/epg-item-description/epg-item-description.component';
import { InfoOverlayComponent } from './components/info-overlay/info-overlay.component';
const routes: Routes = [{ path: '', component: VideoPlayerComponent }]; const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
@@ -19,6 +20,7 @@ const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
EpgItemDescriptionComponent, EpgItemDescriptionComponent,
EpgListComponent, EpgListComponent,
EpgListItemComponent, EpgListItemComponent,
InfoOverlayComponent,
HtmlVideoPlayerComponent, HtmlVideoPlayerComponent,
VideoPlayerComponent, VideoPlayerComponent,
VjsPlayerComponent, VjsPlayerComponent,