@@ -77,6 +77,18 @@ interface Player extends YT.Player {
77
77
// The only field available is destroy and addEventListener.
78
78
type UninitializedPlayer = Pick < Player , 'videoId' | 'destroy' | 'addEventListener' > ;
79
79
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
+
80
92
/**
81
93
* Angular component that renders a YouTube player via the YouTube player
82
94
* iframe API.
@@ -160,6 +172,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
160
172
private _destroyed = new Subject < void > ( ) ;
161
173
private _player : Player | undefined ;
162
174
private _existingApiReadyCallback : ( ( ) => void ) | undefined ;
175
+ private _pendingPlayerState : PendingPlayerState | undefined ;
163
176
164
177
constructor (
165
178
private _ngZone : NgZone ,
@@ -218,7 +231,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
218
231
} ) , takeUntil ( this . _destroyed ) , publish ( ) ) ;
219
232
220
233
// 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
+ } ) ;
222
243
223
244
bindSizeToPlayer ( playerObs , this . _width , this . _height ) ;
224
245
@@ -289,71 +310,112 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
289
310
playVideo ( ) {
290
311
if ( this . _player ) {
291
312
this . _player . playVideo ( ) ;
313
+ } else {
314
+ this . _getPendingState ( ) . playbackState = YT . PlayerState . PLAYING ;
292
315
}
293
316
}
294
317
295
318
/** See https://developers.google.com/youtube/iframe_api_reference#pauseVideo */
296
319
pauseVideo ( ) {
297
320
if ( this . _player ) {
298
321
this . _player . pauseVideo ( ) ;
322
+ } else {
323
+ this . _getPendingState ( ) . playbackState = YT . PlayerState . PAUSED ;
299
324
}
300
325
}
301
326
302
327
/** See https://developers.google.com/youtube/iframe_api_reference#stopVideo */
303
328
stopVideo ( ) {
304
329
if ( this . _player ) {
305
330
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 ;
306
334
}
307
335
}
308
336
309
337
/** See https://developers.google.com/youtube/iframe_api_reference#seekTo */
310
338
seekTo ( seconds : number , allowSeekAhead : boolean ) {
311
339
if ( this . _player ) {
312
340
this . _player . seekTo ( seconds , allowSeekAhead ) ;
341
+ } else {
342
+ this . _getPendingState ( ) . seek = { seconds, allowSeekAhead} ;
313
343
}
314
344
}
315
345
316
346
/** See https://developers.google.com/youtube/iframe_api_reference#mute */
317
347
mute ( ) {
318
348
if ( this . _player ) {
319
349
this . _player . mute ( ) ;
350
+ } else {
351
+ this . _getPendingState ( ) . muted = true ;
320
352
}
321
353
}
322
354
323
355
/** See https://developers.google.com/youtube/iframe_api_reference#unMute */
324
356
unMute ( ) {
325
357
if ( this . _player ) {
326
358
this . _player . unMute ( ) ;
359
+ } else {
360
+ this . _getPendingState ( ) . muted = false ;
327
361
}
328
362
}
329
363
330
364
/** See https://developers.google.com/youtube/iframe_api_reference#isMuted */
331
365
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 ;
333
375
}
334
376
335
377
/** See https://developers.google.com/youtube/iframe_api_reference#setVolume */
336
378
setVolume ( volume : number ) {
337
379
if ( this . _player ) {
338
380
this . _player . setVolume ( volume ) ;
381
+ } else {
382
+ this . _getPendingState ( ) . volume = volume ;
339
383
}
340
384
}
341
385
342
386
/** See https://developers.google.com/youtube/iframe_api_reference#getVolume */
343
387
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 ;
345
397
}
346
398
347
399
/** See https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate */
348
400
setPlaybackRate ( playbackRate : number ) {
349
401
if ( this . _player ) {
350
402
return this . _player . setPlaybackRate ( playbackRate ) ;
403
+ } else {
404
+ this . _getPendingState ( ) . playbackRate = playbackRate ;
351
405
}
352
406
}
353
407
354
408
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate */
355
409
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 ;
357
419
}
358
420
359
421
/** See https://developers.google.com/youtube/iframe_api_reference#getAvailablePlaybackRates */
@@ -372,12 +434,28 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
372
434
return undefined ;
373
435
}
374
436
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 ;
376
446
}
377
447
378
448
/** See https://developers.google.com/youtube/iframe_api_reference#getCurrentTime */
379
449
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 ;
381
459
}
382
460
383
461
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality */
@@ -404,6 +482,42 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
404
482
getVideoEmbedCode ( ) : string {
405
483
return this . _player ? this . _player . getVideoEmbedCode ( ) : '' ;
406
484
}
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
+ }
407
521
}
408
522
409
523
/** Listens to changes to the given width and height and sets it on the player. */
0 commit comments