diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4275caab7..b8bba4f41 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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, diff --git a/src/app/channel-list-container/channel-list-container.component.html b/src/app/channel-list-container/channel-list-container.component.html index 08c2e14e1..fe0ebc46b 100644 --- a/src/app/channel-list-container/channel-list-container.component.html +++ b/src/app/channel-list-container/channel-list-container.component.html @@ -25,7 +25,8 @@ { }); }); - 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); }); diff --git a/src/app/channel-list-container/channel-list-container.component.ts b/src/app/channel-list-container/channel-list-container.component.ts index be1247b62..5c32a936a 100644 --- a/src/app/channel-list-container/channel-list-container.component.ts +++ b/src/app/channel-list-container/channel-list-container.component.ts @@ -32,9 +32,6 @@ export class ChannelListContainerComponent { /** Selected channel */ selected: Channel; - /** Emits on channel change */ - @Output() changeChannel: EventEmitter = new EventEmitter(); - /** List with favorited channels */ favorites$: Observable = 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; + } } diff --git a/src/app/video-player/video-player.component.css b/src/app/video-player/video-player.component.css index cb83ffe11..b755be422 100644 --- a/src/app/video-player/video-player.component.css +++ b/src/app/video-player/video-player.component.css @@ -1,8 +1,3 @@ -#video-player { - width: 100%; - height: calc(100vh - 100px); -} - .main-container { position: absolute; top: 0; diff --git a/src/app/video-player/video-player.component.html b/src/app/video-player/video-player.component.html index 623263b7c..b5caf3d7d 100644 --- a/src/app/video-player/video-player.component.html +++ b/src/app/video-player/video-player.component.html @@ -8,10 +8,7 @@ disableClose > - + @@ -48,27 +45,24 @@ - - - + + - + [channel]="activeChannel$ | async" + > + diff --git a/src/app/video-player/video-player.component.spec.ts b/src/app/video-player/video-player.component.spec.ts index 51f4012f4..dde9eb8b9 100644 --- a/src/app/video-player/video-player.component.spec.ts +++ b/src/app/video-player/video-player.component.spec.ts @@ -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); }); }); diff --git a/src/app/video-player/video-player.component.ts b/src/app/video-player/video-player.component.ts index 326cce8f4..d3a6a5544 100644 --- a/src/app/video-player/video-player.component.ts +++ b/src/app/video-player/video-player.component.ts @@ -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 = this.channelQuery + .select((state) => state.active) + .pipe(tap((channel) => (this.channelTitle = channel?.name))); + /** Channels list */ channels$: Observable = this.channelQuery.selectAll(); + /** Name of the selected channel */ + channelTitle: string; + /** Favorites list */ favorites$: Observable = this.channelQuery.select( (store) => store.favorites ); - /** Video player DOM element */ - @ViewChild('videoPlayer', { static: false }) - videoPlayer: ElementRef; - - /** HLS object */ - hls = new Hls(); - - /** Name of the selected channel */ - channelTitle: string; - - /** Active selected channel */ - activeChannel$: Observable; + /** 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 */