import { AfterViewInit , ChangeDetectorRef , Component , ElementRef , EventEmitter , forwardRef , Inject , Input , OnChanges , OnDestroy , Output , PLATFORM_ID , SimpleChanges , ViewChild , } from '@angular/core' ; import { PLAYER_ANIMATIONS } from './player.animations' ; import { VideoPlayerService , VideoSource } from './player.service' ; import * as Plyr from 'plyr' ; import { PlyrComponent } from 'ngx-plyr' ; import { isPlatformBrowser } from '@angular/common' ; import { BehaviorSubject , combineLatest , Observable , Subscription } from 'rxjs' ; import { Session } from '../../../../services/session' ; import { AutoProgressVideoService } from '../video/auto-progress-overlay/auto-progress-video.service' ; import { map , take } from 'rxjs/operators' ; import { HlsjsPlyrDriver } from './hls-driver' ; @ Component ( { selector : 'm-videoPlayer' , templateUrl : 'player.component.html' , animations : PLAYER_ANIMATIONS , providers : [ VideoPlayerService , Session ] , } ) export class MindsVideoPlayerComponent implements OnChanges , OnDestroy { /**
     * MH: dislike having to emit an event to open modal, but this is
     * the quickest work around for now
     */ @ Output ( ) mediaModalRequested : EventEmitter < void > = new EventEmitter ( ) ; /**
     * Modal needs to know if we have left full screen
     */ @ Output ( ) fullScreenChange : EventEmitter < Event > = new EventEmitter ( ) ; /**
     * Dimension change event for parent components
     */ @ Output ( ) dimensions : EventEmitter < any > = new EventEmitter < any > ( ) ; /**
     * Controls events
     */ @ Output ( ) onControlsShown : EventEmitter < any > = new EventEmitter < any > ( ) ; @ Output ( ) onControlsHidden : EventEmitter < any > = new EventEmitter < any > ( ) ; /**
     * Autoplay (if set to false, then placeholder will be displayed)
     * calling .play() will override this
     */ @ Input ( ) set autoplay ( autoplay ) { if ( autoplay ) { this . service . isPlayable$ . next ( true ) ; } } @ Input ( ) embedded ? : boolean = false ; /**
     * This is the video player component
     */ player : PlyrComponent ; useEmptySource : boolean = false ; emptySource = { id : null , type : null , size : 0 , src : '' , } ; @ ViewChild ( PlyrComponent ) set _player ( player ) { this . player = player ; } /**
     * If the plyr component should be displayed
     */ showPlyr = false ; /**
     * Plyr driver detrmined by source types (detects hls)
     */ plyrDriver$ : Observable < HlsjsPlyrDriver | null > = this . service . sources$ . pipe ( map ( sources => { if ( sources [ 0 ] . type === 'application/vnd.apple.mpegURL' && isPlatformBrowser ( this . platformId ) && ! document . createElement ( 'video' ) . canPlayType ( 'application/vnd.apple.mpegURL' ) ) { return new HlsjsPlyrDriver ( true ) ; } return null ; } ) ) ; /**
     * Options for Plyr to use
     */ options : Plyr . Options = { controls : [ 'play-large' , 'play' , 'progress' , 'current-time' , 'mute' , 'volume' , 'captions' , 'settings' , 'airplay' , 'fullscreen' , ] , autoplay : true , muted : false , hideControls : true , storage : { enabled : false } , } ; /**
     * Flag that gets set to true in ngAfterViewInit
     */ protected init : boolean = false ; subscriptions : Subscription [ ] = [ this . service . onReady$ . subscribe ( ( ) => { this . cd . markForCheck ( ) ; this . cd . detectChanges ( ) ; } ) , ] ; constructor ( public elementRef , private service , private cd , public autoProgress , @ Inject ( PLATFORM_ID ) private platformId ) { } ngOnInit ( ) { this . subscriptions . push ( combineLatest ( [ this . service . isPlayable$ , this . service . sources$ , ] ) . subscribe ( ( [ isPlayable , sources ] ) => { this . showPlyr = isPlatformBrowser ( this . platformId ) && isPlayable && sources . length > 0 ; } ) ) ; } ngOnChanges ( changes ) { if ( isPlatformBrowser ( this . platformId ) ) { this . service . load ( ) ; } } ngOnDestroy ( ) { this . autoProgress . cancel ( ) ; for ( const subscription of this . subscriptions ) { subscription . unsubscribe ( ) ; } } @ Input ( 'guid' ) set guid ( guid ) { const oldGuid = this . service . guid ; this . service . setGuid ( guid ) ; } @ Input ( 'isModal' ) set isModal ( isModal ) { this . service . setIsModal ( isModal ) ; } @ Input ( 'shouldPlayInModal' ) set shouldPlayInModal ( shouldPlayInModal ) { this . service . setShouldPlayInModal ( shouldPlayInModal ) ; } get sources$ ( ) : BehaviorSubject < VideoSource [ ] > { return this . service . sources$ ; } get status ( ) : string { return this . service . status ; } get isModal ( ) : boolean { return this . service . isModal ; } get awaitingTranscode ( ) : Observable < boolean > { return this . service . awaitingTranscode ( ) ; } param event onPlayed ( event ) { } param e return onPlaceholderClick ( e ) { if ( this . player && this . service . isPlayable$ . getValue ( ) ) { this . play ( { muted : false , hideControls : false } ) ; return ; } if ( this . service . shouldPlayInModal ) { return this . mediaModalRequested . next ( ) ; } this . service . isPlayable$ . next ( true ) ; } unmute ( ) { this . options . muted = false ; } mute ( ) { this . options . muted = true ; } isMuted ( ) { return this . player ? this . player . player . muted : false ; } async play ( opts ) { this . options . muted = opts . muted ; this . options . hideControls = opts . hideControls ; this . service . isPlayable$ . next ( true ) ; if ( this . player ) { try { this . player . player . muted = this . options . muted ; await this . player . player . play ( ) ; } catch ( e ) { } } } return pause ( ) { if ( this . player ) { this . player . player . pause ( ) ; this . removeSources ( ) ; } } isPlaying ( ) { return this . player ? this . player . player . playing : false ; } stop ( ) { if ( this . player ) { this . player . player . stop ( ) ; } } param e emitDimensions ( e ) { try { const media = e . detail . plyr . media ; if ( ! media ) { return ; } this . dimensions . next ( { width : media . videoWidth , height : media . videoHeight , } ) ; } catch ( e ) { console . info ( 'Error emitting dimensions' , e ) ; } } onReady ( ) { } onVolumeChange ( ) { } onPlay ( ) { } removeSources ( ) { const sources = this . elementRef . nativeElement . getElementsByTagName ( 'source' ) ; for ( const source of sources ) { source . remove ( ) ; } const video = this . elementRef . nativeElement . getElementsByTagName ( 'video' ) [ 0 ] ; if ( video ) { try { video . load ( ) ; } catch ( err ) { } } this . service . isPlayable$ . next ( false ) ; } onEnded ( $event ) { this . autoProgress . next ( ) ; } /**
     * Called on Plyr seek.
     */ onSeeking ( ) { this . subscriptions . push ( this . autoProgress . timer$ . pipe ( take ( 1 ) ) . subscribe ( timer => { if ( timer > 0 ) { this . autoProgress . cancel ( ) ; } } ) ) ; } }