From 27fae292721bcc48b68c16fe5c7ac5e928eaa57d Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 6 Dec 2019 23:21:57 +0100 Subject: [PATCH] fix(youtube-player): avoid setInterval change detection when player is created (#17894) When we create a `Player` object for the `youtube-player` component, the YouTube API kicks off a 250ms `setInterval` which will trigger change detection even if the video is passive on the page. These change run the initialization code outside the `NgZone` so that doesn't happen. --- src/youtube-player/youtube-player.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts index 03a98b2e29a4..36c665750705 100644 --- a/src/youtube-player/youtube-player.ts +++ b/src/youtube-player/youtube-player.ts @@ -38,6 +38,7 @@ import { OperatorFunction, pipe, Subject, + of, } from 'rxjs'; import { @@ -210,7 +211,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { const endSecondsObs = this._endSeconds.pipe(startWith(undefined)); const suggestedQualityObs = this._suggestedQuality.pipe(startWith(undefined)); - /** An observable of the currently loaded player. */ + // An observable of the currently loaded player. const playerObs = createPlayerObservable( this._youtubeContainer, @@ -219,6 +220,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit { widthObs, heightObs, this.createEventsBoundInZone(), + this._ngZone ).pipe(waitUntilReady(), takeUntil(this._destroyed), publish()); // Set up side effects to bind inputs to the player. @@ -480,6 +482,7 @@ function createPlayerObservable( widthObs: Observable, heightObs: Observable, events: YT.Events, + ngZone: NgZone ): Observable { const playerOptions = @@ -489,7 +492,7 @@ function createPlayerObservable( map(([videoId, [width, height]]) => videoId ? ({videoId, width, height, events}) : undefined), ); - return combineLatest([youtubeContainer, playerOptions]) + return combineLatest([youtubeContainer, playerOptions, of(ngZone)]) .pipe( skipUntilRememberLatest(iframeApiAvailableObs), scan(syncPlayerState, undefined), @@ -507,7 +510,7 @@ function skipUntilRememberLatest(notifier: Observable): MonoTypeOper /** Destroy the player if there are no options, or create the player if there are options. */ function syncPlayerState( player: UninitializedPlayer | undefined, - [container, videoOptions]: [HTMLElement, YT.PlayerOptions | undefined], + [container, videoOptions, ngZone]: [HTMLElement, YT.PlayerOptions | undefined, NgZone], ): UninitializedPlayer | undefined { if (!videoOptions) { if (player) { @@ -519,7 +522,10 @@ function syncPlayerState( return player; } - const newPlayer: UninitializedPlayer = new YT.Player(container, videoOptions); + // 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)); // Bind videoId for future use. newPlayer.videoId = videoOptions.videoId; return newPlayer;