refactor: improve component structure of players

This commit is contained in:
4gray committed 2020-12-13 00:19:09 +01:00
1 parent e9714c77b7
commit c8964adb14
8 files changed
+54 -94

No files matched your search

+2
View File
@@ -18,6 +18,7 @@ import { AppComponent } from './app.component';
import { VideoPlayerComponent } from './video-player/video-player.component';
import { ChannelListContainerComponent } from './channel-list-container/channel-list-container.component';
import { VjsPlayerComponent } from './vjs-player/vjs-player.component';
import { HtmlVideoPlayerComponent } from './html-video-player/html-video-player.component';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
@@ -30,6 +31,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
ChannelListContainerComponent,
VideoPlayerComponent,
VjsPlayerComponent,
HtmlVideoPlayerComponent,
],
imports: [
AppRoutingModule,
@@ -25,7 +25,8 @@
<mat-list-item
*cdkVirtualFor="
let channel of _channelList | filterBy: searchTerm;
index as i
index as i;
trackBy: trackByFn
"
[class.active]="selected?.id === channel.id"
(click)="selectChannel(channel)"
@@ -130,15 +130,10 @@ describe('ChannelListContainerComponent', () => {
});
});
it('should update store and emit event after channel was selected', () => {
it('should update store after channel was selected', () => {
spyOn(store, 'update');
spyOn(component.changeChannel, 'emit');
component.selectChannel(component._channelList[0]);
fixture.detectChanges();
expect(component.changeChannel.emit).toHaveBeenCalledTimes(1);
expect(component.changeChannel.emit).toHaveBeenCalledWith(
component._channelList[0]
);
expect(store.update).toHaveBeenCalledTimes(1);
});
@@ -32,9 +32,6 @@ export class ChannelListContainerComponent {
/** Selected channel */
selected: Channel;
/** Emits on channel change */
@Output() changeChannel: EventEmitter<Channel> = new EventEmitter();
/** List with favorited channels */
favorites$: Observable<Channel[]> = this.channelQuery.select((store) =>
this.channelQuery
@@ -65,7 +62,6 @@ export class ChannelListContainerComponent {
*/
selectChannel(channel: Channel): void {
this.selected = channel;
this.changeChannel.emit(channel);
this.channelStore.update((store) => ({
...store,
active: channel,
@@ -82,4 +78,13 @@ export class ChannelListContainerComponent {
this.snackBar.open('Favorites were updated!', null, { duration: 2000 });
this.channelStore.updateFavorite(channel);
}
/**
* Required for change detection mechanism to nor re-init the whole component after changes
* @param index index of the channel item
* @param channel channel object
*/
trackByFn(index: number, channel: Channel): string {
return channel.id;
}
}
@@ -1,8 +1,3 @@
#video-player {
width: 100%;
height: calc(100vh - 100px);
}
.main-container {
position: absolute;
top: 0;
@@ -8,10 +8,7 @@
disableClose
>
<ng-container *ngIf="channels$ | async as channels">
<app-channel-list-container
[channelList]="channels"
(changeChannel)="playChannel($event)"
>
<app-channel-list-container [channelList]="channels">
</app-channel-list-container>
</ng-container>
</mat-drawer>
@@ -48,27 +45,24 @@
</button>
</mat-toolbar>
<ng-container *ngIf="player === 'videojs'">
<app-vjs-player
[options]="{
sources: [
{
src: activeChannel.url,
type: 'application/x-mpegURL'
}
]
}"
></app-vjs-player>
</ng-container>
<!-- video.js player -->
<app-vjs-player
*ngIf="player === 'videojs'"
[options]="{
sources: [
{
src: activeChannel.url,
type: 'application/x-mpegURL'
}
]
}"
></app-vjs-player>
</ng-container>
<!-- default html player component -->
<video
#videoPlayer
<app-html-video-player
*ngIf="player === 'html5'"
id="video-player"
autoplay="true"
controls="true"
muted="muted"
></video>
[channel]="activeChannel$ | async"
>
</app-html-video-player>
</mat-drawer-content>
</mat-drawer-container>
@@ -1,4 +1,4 @@
/* tslint:disable:no-unused-variable */
/* eslint-disable @typescript-eslint/unbound-method */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { VideoPlayerComponent } from './video-player.component';
import { MockComponent, MockModule } from 'ng-mocks';
@@ -7,10 +7,11 @@ import { VjsPlayerComponent } from '../vjs-player/vjs-player.component';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ChannelStore } from '../state/channel.store';
import * as MOCKED_PLAYLIST from '../../mocks/playlist.json';
import { createChannel } from '../state';
import { HtmlVideoPlayerComponent } from 'app/html-video-player/html-video-player.component';
class MatSnackBarStub {
open(): void {}
@@ -25,14 +26,13 @@ describe('VideoPlayerComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MockComponent(HtmlVideoPlayerComponent),
MockComponent(VjsPlayerComponent),
MockComponent(VideoPlayerComponent),
MockComponent(ChannelListContainerComponent),
VideoPlayerComponent,
],
providers: [
{ provide: MatSnackBar, useClass: MatSnackBarStub },
],
providers: [{ provide: MatSnackBar, useClass: MatSnackBarStub }],
imports: [
MockModule(MatSidenavModule),
MockModule(MatIconModule),
@@ -61,16 +61,15 @@ describe('VideoPlayerComponent', () => {
it('should check default component settings', () => {
fixture.detectChanges();
expect(component.videoPlayer).toBeDefined();
expect(component.player).toEqual('html5');
});
it('should update store after channel was faved', () => {
spyOn(store, 'updateFavorite');
const [firstChannel] = channels;
component.addToFavorites(firstChannel);
fixture.detectChanges();
expect(store.updateFavorite).toBeCalledTimes(1);
expect(store.updateFavorite).toBeCalledWith(firstChannel)
spyOn(store, 'updateFavorite');
const [firstChannel] = channels;
component.addToFavorites(firstChannel);
fixture.detectChanges();
expect(store.updateFavorite).toBeCalledTimes(1);
expect(store.updateFavorite).toBeCalledWith(firstChannel);
});
});
+12 -43
View File
@@ -1,7 +1,7 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import * as Hls from 'hls.js';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ChannelQuery, Channel, ChannelStore } from '../state';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { MatSidenav } from '@angular/material/sidenav';
import { ElectronService } from '../services/electron.service';
import { StorageMap } from '@ngx-pwa/local-storage';
@@ -17,33 +17,28 @@ export const SETTINGS_STORE_KEY = 'settings';
styleUrls: ['./video-player.component.css'],
})
export class VideoPlayerComponent implements OnInit {
/** Active selected channel */
activeChannel$: Observable<Channel> = this.channelQuery
.select((state) => state.active)
.pipe(tap((channel) => (this.channelTitle = channel?.name)));
/** Channels list */
channels$: Observable<Channel[]> = this.channelQuery.selectAll();
/** Name of the selected channel */
channelTitle: string;
/** Favorites list */
favorites$: Observable<string[]> = this.channelQuery.select(
(store) => store.favorites
);
/** Video player DOM element */
@ViewChild('videoPlayer', { static: false })
videoPlayer: ElementRef<HTMLVideoElement>;
/** HLS object */
hls = new Hls();
/** Name of the selected channel */
channelTitle: string;
/** Active selected channel */
activeChannel$: Observable<Channel>;
/** Selected video player component */
player: VideoPlayerType = 'html5';
/** Sidebar object */
@ViewChild('sidenav') sideNav: MatSidenav;
/** Selected video player component */
player: VideoPlayerType = 'html5';
/**
* Creates an instance of VideoPlayerComponent
* @param channelQuery akita's channel query
@@ -64,7 +59,6 @@ export class VideoPlayerComponent implements OnInit {
* Sets video player and subscribes to channel list from the store
*/
ngOnInit(): void {
this.activeChannel$ = this.channelQuery.select((state) => state.active);
this.applySettings();
}
@@ -86,31 +80,6 @@ export class VideoPlayerComponent implements OnInit {
this.sideNav.close();
}
/**
* Starts to play the given channel
* @param channel given channel object
*/
playChannel(channel: Channel): void {
if (Hls.isSupported()) {
console.log('... switching channel to ', channel.name, channel.url);
this.hls.loadSource(channel.url);
this.hls.attachMedia(this.videoPlayer.nativeElement);
this.channelTitle = channel.name;
} else if (
this.videoPlayer.nativeElement.canPlayType(
'application/vnd.apple.mpegurl'
)
) {
this.videoPlayer.nativeElement.src = channel.url;
this.videoPlayer.nativeElement.addEventListener(
'loadedmetadata',
() => {
this.videoPlayer.nativeElement.play();
}
);
}
}
/**
* Opens about application dialog
*/