@@ -77,6 +77,18 @@ interface Player extends YT.Player {
7777// The only field available is destroy and addEventListener.
7878type UninitializedPlayer = Pick < Player , 'videoId' | 'destroy' | 'addEventListener' > ;
7979
80+ /**
81+ * Object used to store the state of the player if the
82+ * user tries to interact with the API before it has been loaded.
83+ */
84+ interface PendingPlayerState {
85+ playbackState ?: YT . PlayerState . PLAYING | YT . PlayerState . PAUSED | YT . PlayerState . CUED ;
86+ playbackRate ?: number ;
87+ volume ?: number ;
88+ muted ?: boolean ;
89+ seek ?: { seconds : number , allowSeekAhead : boolean } ;
90+ }
91+
8092/**
8193 * Angular component that renders a YouTube player via the YouTube player
8294 * iframe API.
@@ -160,6 +172,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
160172 private _destroyed = new Subject < void > ( ) ;
161173 private _player : Player | undefined ;
162174 private _existingApiReadyCallback : ( ( ) => void ) | undefined ;
175+ private _pendingPlayerState : PendingPlayerState | undefined ;
163176
164177 constructor (
165178 private _ngZone : NgZone ,
@@ -218,7 +231,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
218231 } ) , takeUntil ( this . _destroyed ) , publish ( ) ) ;
219232
220233 // Set up side effects to bind inputs to the player.
221- playerObs . subscribe ( player => this . _player = player ) ;
234+ playerObs . subscribe ( player => {
235+ this . _player = player ;
236+
237+ if ( player && this . _pendingPlayerState ) {
238+ this . _initializePlayer ( player , this . _pendingPlayerState ) ;
239+ }
240+
241+ this . _pendingPlayerState = undefined ;
242+ } ) ;
222243
223244 bindSizeToPlayer ( playerObs , this . _width , this . _height ) ;
224245
@@ -289,71 +310,112 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
289310 playVideo ( ) {
290311 if ( this . _player ) {
291312 this . _player . playVideo ( ) ;
313+ } else {
314+ this . _getPendingState ( ) . playbackState = YT . PlayerState . PLAYING ;
292315 }
293316 }
294317
295318 /** See https://developers.google.com/youtube/iframe_api_reference#pauseVideo */
296319 pauseVideo ( ) {
297320 if ( this . _player ) {
298321 this . _player . pauseVideo ( ) ;
322+ } else {
323+ this . _getPendingState ( ) . playbackState = YT . PlayerState . PAUSED ;
299324 }
300325 }
301326
302327 /** See https://developers.google.com/youtube/iframe_api_reference#stopVideo */
303328 stopVideo ( ) {
304329 if ( this . _player ) {
305330 this . _player . stopVideo ( ) ;
331+ } else {
332+ // It seems like YouTube sets the player to CUED when it's stopped.
333+ this . _getPendingState ( ) . playbackState = YT . PlayerState . CUED ;
306334 }
307335 }
308336
309337 /** See https://developers.google.com/youtube/iframe_api_reference#seekTo */
310338 seekTo ( seconds : number , allowSeekAhead : boolean ) {
311339 if ( this . _player ) {
312340 this . _player . seekTo ( seconds , allowSeekAhead ) ;
341+ } else {
342+ this . _getPendingState ( ) . seek = { seconds, allowSeekAhead} ;
313343 }
314344 }
315345
316346 /** See https://developers.google.com/youtube/iframe_api_reference#mute */
317347 mute ( ) {
318348 if ( this . _player ) {
319349 this . _player . mute ( ) ;
350+ } else {
351+ this . _getPendingState ( ) . muted = true ;
320352 }
321353 }
322354
323355 /** See https://developers.google.com/youtube/iframe_api_reference#unMute */
324356 unMute ( ) {
325357 if ( this . _player ) {
326358 this . _player . unMute ( ) ;
359+ } else {
360+ this . _getPendingState ( ) . muted = false ;
327361 }
328362 }
329363
330364 /** See https://developers.google.com/youtube/iframe_api_reference#isMuted */
331365 isMuted ( ) : boolean {
332- return ! this . _player || this . _player . isMuted ( ) ;
366+ if ( this . _player ) {
367+ return this . _player . isMuted ( ) ;
368+ }
369+
370+ if ( this . _pendingPlayerState ) {
371+ return ! ! this . _pendingPlayerState . muted ;
372+ }
373+
374+ return false ;
333375 }
334376
335377 /** See https://developers.google.com/youtube/iframe_api_reference#setVolume */
336378 setVolume ( volume : number ) {
337379 if ( this . _player ) {
338380 this . _player . setVolume ( volume ) ;
381+ } else {
382+ this . _getPendingState ( ) . volume = volume ;
339383 }
340384 }
341385
342386 /** See https://developers.google.com/youtube/iframe_api_reference#getVolume */
343387 getVolume ( ) : number {
344- return this . _player ? this . _player . getVolume ( ) : 0 ;
388+ if ( this . _player ) {
389+ return this . _player . getVolume ( ) ;
390+ }
391+
392+ if ( this . _pendingPlayerState && this . _pendingPlayerState . volume != null ) {
393+ return this . _pendingPlayerState . volume ;
394+ }
395+
396+ return 0 ;
345397 }
346398
347399 /** See https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate */
348400 setPlaybackRate ( playbackRate : number ) {
349401 if ( this . _player ) {
350402 return this . _player . setPlaybackRate ( playbackRate ) ;
403+ } else {
404+ this . _getPendingState ( ) . playbackRate = playbackRate ;
351405 }
352406 }
353407
354408 /** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate */
355409 getPlaybackRate ( ) : number {
356- return this . _player ? this . _player . getPlaybackRate ( ) : 0 ;
410+ if ( this . _player ) {
411+ return this . _player . getPlaybackRate ( ) ;
412+ }
413+
414+ if ( this . _pendingPlayerState && this . _pendingPlayerState . playbackRate != null ) {
415+ return this . _pendingPlayerState . playbackRate ;
416+ }
417+
418+ return 0 ;
357419 }
358420
359421 /** See https://developers.google.com/youtube/iframe_api_reference#getAvailablePlaybackRates */
@@ -372,12 +434,28 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
372434 return undefined ;
373435 }
374436
375- return this . _player ? this . _player . getPlayerState ( ) : YT . PlayerState . UNSTARTED ;
437+ if ( this . _player ) {
438+ return this . _player . getPlayerState ( ) ;
439+ }
440+
441+ if ( this . _pendingPlayerState && this . _pendingPlayerState . playbackState != null ) {
442+ return this . _pendingPlayerState . playbackState ;
443+ }
444+
445+ return YT . PlayerState . UNSTARTED ;
376446 }
377447
378448 /** See https://developers.google.com/youtube/iframe_api_reference#getCurrentTime */
379449 getCurrentTime ( ) : number {
380- return this . _player ? this . _player . getCurrentTime ( ) : 0 ;
450+ if ( this . _player ) {
451+ return this . _player . getCurrentTime ( ) ;
452+ }
453+
454+ if ( this . _pendingPlayerState && this . _pendingPlayerState . seek ) {
455+ return this . _pendingPlayerState . seek . seconds ;
456+ }
457+
458+ return 0 ;
381459 }
382460
383461 /** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality */
@@ -404,6 +482,42 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
404482 getVideoEmbedCode ( ) : string {
405483 return this . _player ? this . _player . getVideoEmbedCode ( ) : '' ;
406484 }
485+
486+ /** Gets an object that should be used to store the temporary API state. */
487+ private _getPendingState ( ) : PendingPlayerState {
488+ if ( ! this . _pendingPlayerState ) {
489+ this . _pendingPlayerState = { } ;
490+ }
491+
492+ return this . _pendingPlayerState ;
493+ }
494+
495+ /** Initializes a player from a temporary state. */
496+ private _initializePlayer ( player : YT . Player , state : PendingPlayerState ) : void {
497+ const { playbackState, playbackRate, volume, muted, seek} = state ;
498+
499+ switch ( playbackState ) {
500+ case YT . PlayerState . PLAYING : player . playVideo ( ) ; break ;
501+ case YT . PlayerState . PAUSED : player . pauseVideo ( ) ; break ;
502+ case YT . PlayerState . CUED : player . stopVideo ( ) ; break ;
503+ }
504+
505+ if ( playbackRate != null ) {
506+ player . setPlaybackRate ( playbackRate ) ;
507+ }
508+
509+ if ( volume != null ) {
510+ player . setVolume ( volume ) ;
511+ }
512+
513+ if ( muted != null ) {
514+ muted ? player . mute ( ) : player . unMute ( ) ;
515+ }
516+
517+ if ( seek != null ) {
518+ player . seekTo ( seek . seconds , seek . allowSeekAhead ) ;
519+ }
520+ }
407521}
408522
409523/** Listens to changes to the given width and height and sets it on the player. */
0 commit comments