From fea5dcf7c06d26bba0254c890ee6eecb4f30e82e Mon Sep 17 00:00:00 2001 From: Martin Croker Date: Wed, 29 Jan 2020 16:33:46 +0000 Subject: [PATCH 1/3] Add playerVars @Inputs --- src/youtube-player/youtube-player.ts | 72 +++++++++++++++++++--------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index 071a372e4492..5b0254654a60 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -26,7 +26,7 @@ import { Inject, PLATFORM_ID, } from '@angular/core'; -import {isPlatformBrowser} from '@angular/common'; +import { isPlatformBrowser } from '@angular/common'; import { combineLatest, @@ -106,6 +106,24 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { } private _height = new BehaviorSubject(DEFAULT_PLAYER_HEIGHT); + /** PlayerVars of video player */ + @Input() + get controls(): YT.Controls | undefined { return this._playerVars.value.controls; } + set controls(controls: YT.Controls | undefined) { + this._playerVars.next({ ...this._playerVars.value, controls } || {}); + } + @Input() + get autoplay(): YT.AutoPlay | undefined { return this._playerVars.value.autoplay; } + set autoplay(autoplay: YT.AutoPlay | undefined) { + this._playerVars.next({ ...this._playerVars.value, autoplay } || {}); + } + @Input() + get disablekb(): YT.KeyboardControls | undefined { return this._playerVars.value.disablekb; } + set disablekb(disablekb: YT.KeyboardControls | undefined) { + this._playerVars.next({ ...this._playerVars.value, disablekb } || {}); + } + private _playerVars = new BehaviorSubject({}); + /** Width of video player */ @Input() get width(): number | undefined { return this._width.value; } @@ -151,7 +169,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { @Output() playbackRateChange = new EventEmitter(); /** The element that will be replaced by the iframe. */ - @ViewChild('youtubeContainer') + @ViewChild('youtubeContainer', { static: false }) youtubeContainer: ElementRef; /** Whether we're currently rendering inside a browser. */ @@ -171,7 +189,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { // @breaking-change 10.0.0 Remove null check for `platformId`. this._isBrowser = - platformId ? isPlatformBrowser(platformId) : typeof window === 'object' && !!window; + platformId ? isPlatformBrowser(platformId) : typeof window === 'object' && !!window; } ngOnInit() { @@ -184,8 +202,8 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { if (!window.YT) { if (this.showBeforeIframeApiLoads) { throw new Error('Namespace YT not found, cannot construct embedded youtube player. ' + - 'Please install the YouTube Player API Reference for iframe Embeds: ' + - 'https://developers.google.com/youtube/iframe_api_reference'); + 'Please install the YouTube Player API Reference for iframe Embeds: ' + + 'https://developers.google.com/youtube/iframe_api_reference'); } const iframeApiAvailableSubject = new Subject(); @@ -200,6 +218,8 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { iframeApiAvailableObs = iframeApiAvailableSubject.pipe(take(1), startWith(false)); } + + // An observable of the currently loaded player. const playerObs = createPlayerObservable( @@ -208,6 +228,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { iframeApiAvailableObs, this._width, this._height, + this._playerVars, this.createEventsBoundInZone(), this._ngZone ).pipe(waitUntilReady(player => { @@ -269,6 +290,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { this._videoId.complete(); this._height.complete(); + this._playerVars.complete(); this._width.complete(); this._startSeconds.complete(); this._endSeconds.complete(); @@ -279,7 +301,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { } private _runInZone void>(callback: T): - (...args: Parameters) => void { + (...args: Parameters) => void { return (...args: Parameters) => this._ngZone.run(() => callback(...args)); } @@ -413,7 +435,7 @@ function bindSizeToPlayer( heightObs: Observable ) { return combineLatest([playerObs, widthObs, heightObs]) - .subscribe(([player, width, height]) => player && player.setSize(width, height)); + .subscribe(([player, width, height]) => player && player.setSize(width, height)); } /** Listens to changes from the suggested quality and sets it on the given player. */ @@ -426,7 +448,7 @@ function bindSuggestedQualityToPlayer( suggestedQualityObs ]).subscribe( ([player, suggestedQuality]) => - player && suggestedQuality && player.setPlaybackQuality(suggestedQuality)); + player && suggestedQuality && player.setPlaybackQuality(suggestedQuality)); } /** @@ -439,7 +461,7 @@ function waitUntilReady(onAbort: (player: UninitializedPlayer) => void): OperatorFunction { return flatMap(player => { if (!player) { - return observableOf(undefined); + return observableOf(undefined); } if (playerIsReady(player)) { return observableOf(player as Player); @@ -479,22 +501,23 @@ function createPlayerObservable( iframeApiAvailableObs: Observable, widthObs: Observable, heightObs: Observable, + playerVarsObs: Observable, events: YT.Events, ngZone: NgZone ): Observable { const playerOptions = videoIdObs - .pipe( - withLatestFrom(combineLatest([widthObs, heightObs])), - map(([videoId, [width, height]]) => videoId ? ({videoId, width, height, events}) : undefined), - ); + .pipe( + withLatestFrom(combineLatest([widthObs, heightObs, playerVarsObs])), + map(([videoId, [width, height, playerVars]]) => videoId ? ({ videoId, width, height, playerVars, events }) : undefined), + ); return combineLatest([youtubeContainer, playerOptions, of(ngZone)]) - .pipe( - skipUntilRememberLatest(iframeApiAvailableObs), - scan(syncPlayerState, undefined), - distinctUntilChanged()); + .pipe( + skipUntilRememberLatest(iframeApiAvailableObs), + scan(syncPlayerState, undefined), + distinctUntilChanged()); } /** Skips the given observable until the other observable emits true, then emit the latest. */ @@ -523,7 +546,7 @@ function syncPlayerState( // Important! We need to create the Player object outside of the `NgZone`, because it kicks // off a 250ms setInterval which will continually trigger change detection if we don't. const newPlayer: UninitializedPlayer = - ngZone.runOutsideAngular(() => new YT.Player(container, videoOptions)); + ngZone.runOutsideAngular(() => new YT.Player(container, videoOptions)); // Bind videoId for future use. newPlayer.videoId = videoOptions.videoId; return newPlayer; @@ -543,7 +566,7 @@ function bindCueVideoCall( destroyed: Observable, ) { const cueOptionsObs = combineLatest([startSecondsObs, endSecondsObs]) - .pipe(map(([startSeconds, endSeconds]) => ({startSeconds, endSeconds}))); + .pipe(map(([startSeconds, endSeconds]) => ({ startSeconds, endSeconds }))); // Only respond to changes in cue options if the player is not running. const filteredCueOptions = cueOptionsObs @@ -552,15 +575,15 @@ function bindCueVideoCall( // If the video id changed, there's no reason to run 'cue' unless the player // was initialized with a different video id. const changedVideoId = videoIdObs - .pipe(filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId)); + .pipe(filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId)); // If the player changed, there's no reason to run 'cue' unless there are cue options. const changedPlayer = playerObs.pipe( filterOnOther( combineLatest([videoIdObs, cueOptionsObs]), ([videoId, cueOptions], player) => - !!player && - (videoId != player.videoId || !!cueOptions.startSeconds || !!cueOptions.endSeconds))); + !!player && + (videoId != player.videoId || !!cueOptions.startSeconds || !!cueOptions.endSeconds))); merge(changedPlayer, changedVideoId, filteredCueOptions) .pipe( @@ -573,6 +596,11 @@ function bindCueVideoCall( return; } player.videoId = videoId; + console.log({ + videoId, + suggestedQuality, + ...cueOptions, + }) player.cueVideoById({ videoId, suggestedQuality, From b067fef24f2684aaa2416bca8c16f010df6b16f1 Mon Sep 17 00:00:00 2001 From: Martin Croker Date: Wed, 29 Jan 2020 16:40:05 +0000 Subject: [PATCH 2/3] Add playerVars @Inputs (reverse unintended formatting changes) --- src/youtube-player/youtube-player.ts | 47 ++++++++++++---------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index 5b0254654a60..fbbcd6a89e40 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -26,7 +26,7 @@ import { Inject, PLATFORM_ID, } from '@angular/core'; -import { isPlatformBrowser } from '@angular/common'; +import {isPlatformBrowser} from '@angular/common'; import { combineLatest, @@ -169,7 +169,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { @Output() playbackRateChange = new EventEmitter(); /** The element that will be replaced by the iframe. */ - @ViewChild('youtubeContainer', { static: false }) + @ViewChild('youtubeContainer') youtubeContainer: ElementRef; /** Whether we're currently rendering inside a browser. */ @@ -202,8 +202,8 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { if (!window.YT) { if (this.showBeforeIframeApiLoads) { throw new Error('Namespace YT not found, cannot construct embedded youtube player. ' + - 'Please install the YouTube Player API Reference for iframe Embeds: ' + - 'https://developers.google.com/youtube/iframe_api_reference'); + 'Please install the YouTube Player API Reference for iframe Embeds: ' + + 'https://developers.google.com/youtube/iframe_api_reference'); } const iframeApiAvailableSubject = new Subject(); @@ -218,8 +218,6 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { iframeApiAvailableObs = iframeApiAvailableSubject.pipe(take(1), startWith(false)); } - - // An observable of the currently loaded player. const playerObs = createPlayerObservable( @@ -301,7 +299,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { } private _runInZone void>(callback: T): - (...args: Parameters) => void { + (...args: Parameters) => void { return (...args: Parameters) => this._ngZone.run(() => callback(...args)); } @@ -435,7 +433,7 @@ function bindSizeToPlayer( heightObs: Observable ) { return combineLatest([playerObs, widthObs, heightObs]) - .subscribe(([player, width, height]) => player && player.setSize(width, height)); + .subscribe(([player, width, height]) => player && player.setSize(width, height)); } /** Listens to changes from the suggested quality and sets it on the given player. */ @@ -448,7 +446,7 @@ function bindSuggestedQualityToPlayer( suggestedQualityObs ]).subscribe( ([player, suggestedQuality]) => - player && suggestedQuality && player.setPlaybackQuality(suggestedQuality)); + player && suggestedQuality && player.setPlaybackQuality(suggestedQuality)); } /** @@ -458,7 +456,7 @@ function bindSuggestedQualityToPlayer( * it was able to complete. Can be used to clean up any loose references. */ function waitUntilReady(onAbort: (player: UninitializedPlayer) => void): - OperatorFunction { + OperatorFunction { return flatMap(player => { if (!player) { return observableOf(undefined); @@ -508,16 +506,16 @@ function createPlayerObservable( const playerOptions = videoIdObs - .pipe( - withLatestFrom(combineLatest([widthObs, heightObs, playerVarsObs])), - map(([videoId, [width, height, playerVars]]) => videoId ? ({ videoId, width, height, playerVars, events }) : undefined), - ); + .pipe( + withLatestFrom(combineLatest([widthObs, heightObs, playerVarsObs])), + map(([videoId, [width, height, playerVars]]) => videoId ? ({ videoId, width, height, playerVars, events }) : undefined), + ); return combineLatest([youtubeContainer, playerOptions, of(ngZone)]) - .pipe( - skipUntilRememberLatest(iframeApiAvailableObs), - scan(syncPlayerState, undefined), - distinctUntilChanged()); + .pipe( + skipUntilRememberLatest(iframeApiAvailableObs), + scan(syncPlayerState, undefined), + distinctUntilChanged()); } /** Skips the given observable until the other observable emits true, then emit the latest. */ @@ -546,7 +544,7 @@ function syncPlayerState( // Important! We need to create the Player object outside of the `NgZone`, because it kicks // off a 250ms setInterval which will continually trigger change detection if we don't. const newPlayer: UninitializedPlayer = - ngZone.runOutsideAngular(() => new YT.Player(container, videoOptions)); + ngZone.runOutsideAngular(() => new YT.Player(container, videoOptions)); // Bind videoId for future use. newPlayer.videoId = videoOptions.videoId; return newPlayer; @@ -575,15 +573,15 @@ function bindCueVideoCall( // If the video id changed, there's no reason to run 'cue' unless the player // was initialized with a different video id. const changedVideoId = videoIdObs - .pipe(filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId)); + .pipe(filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId)); // If the player changed, there's no reason to run 'cue' unless there are cue options. const changedPlayer = playerObs.pipe( filterOnOther( combineLatest([videoIdObs, cueOptionsObs]), ([videoId, cueOptions], player) => - !!player && - (videoId != player.videoId || !!cueOptions.startSeconds || !!cueOptions.endSeconds))); + !!player && + (videoId != player.videoId || !!cueOptions.startSeconds || !!cueOptions.endSeconds))); merge(changedPlayer, changedVideoId, filteredCueOptions) .pipe( @@ -596,11 +594,6 @@ function bindCueVideoCall( return; } player.videoId = videoId; - console.log({ - videoId, - suggestedQuality, - ...cueOptions, - }) player.cueVideoById({ videoId, suggestedQuality, From 5e86a20fd5e2cfc490edcfef69d37ef9675c6a90 Mon Sep 17 00:00:00 2001 From: Martin Croker Date: Wed, 29 Jan 2020 16:46:42 +0000 Subject: [PATCH 3/3] Add playerVars @Inputs (fix circleci lint issue) --- src/youtube-player/youtube-player.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index fbbcd6a89e40..a77a46620fd0 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -508,7 +508,8 @@ function createPlayerObservable( videoIdObs .pipe( withLatestFrom(combineLatest([widthObs, heightObs, playerVarsObs])), - map(([videoId, [width, height, playerVars]]) => videoId ? ({ videoId, width, height, playerVars, events }) : undefined), + map(([videoId, [width, height, playerVars]]) => + videoId ? ({ videoId, width, height, playerVars, events }) : undefined), ); return combineLatest([youtubeContainer, playerOptions, of(ngZone)])