mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: improve component structure of players
This commit is contained in:
1 parent
e9714c77b7
commit
c8964adb14
8 files changed
+54
-94
No files matched your search
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user