diff --git a/src/dev-app/youtube-player/youtube-player-demo.html b/src/dev-app/youtube-player/youtube-player-demo.html
index 1f08254f95b6..4225e9f7bd9e 100644
--- a/src/dev-app/youtube-player/youtube-player-demo.html
+++ b/src/dev-app/youtube-player/youtube-player-demo.html
@@ -13,9 +13,11 @@
Basic Example
Disable cookies
Disable placeholder
+ Start at 30s
{
const player = noEventsApp.componentInstance.player;
const subscriptions: Subscription[] = [];
- const readySpy = jasmine.createSpy('ready spy');
const stateChangeSpy = jasmine.createSpy('stateChange spy');
const playbackQualityChangeSpy = jasmine.createSpy('playbackQualityChange spy');
const playbackRateChangeSpy = jasmine.createSpy('playbackRateChange spy');
const errorSpy = jasmine.createSpy('error spy');
const apiChangeSpy = jasmine.createSpy('apiChange spy');
- subscriptions.push(player.ready.subscribe(readySpy));
- events.onReady({target: playerSpy});
- expect(readySpy).toHaveBeenCalledWith({target: playerSpy});
-
subscriptions.push(player.stateChange.subscribe(stateChangeSpy));
events.onStateChange({target: playerSpy, data: 5});
expect(stateChangeSpy).toHaveBeenCalledWith({target: playerSpy, data: 5});
diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts
index 0dd2e9525637..0d3bd9febf93 100644
--- a/src/youtube-player/youtube-player.ts
+++ b/src/youtube-player/youtube-player.ts
@@ -29,6 +29,7 @@ import {
CSP_NONCE,
ChangeDetectorRef,
AfterViewInit,
+ EventEmitter,
} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
import {Observable, of as observableOf, Subject, BehaviorSubject, fromEventPattern} from 'rxjs';
@@ -218,22 +219,29 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
*/
@Input() placeholderImageQuality: PlaceholderImageQuality;
- /** Outputs are direct proxies from the player itself. */
- @Output() readonly ready: Observable =
- this._getLazyEmitter('onReady');
+ // Note: ready event can't go through the lazy emitter, because it
+ // happens before the `_playerChanges` stream emits the new player.
+ /** Emits when the player is initialized. */
+ @Output() readonly ready: Observable = new EventEmitter();
+
+ /** Emits when the state of the player has changed. */
@Output() readonly stateChange: Observable =
this._getLazyEmitter('onStateChange');
+ /** Emits when there's an error while initializing the player. */
@Output() readonly error: Observable =
this._getLazyEmitter('onError');
+ /** Emits when the underlying API of the player has changed. */
@Output() readonly apiChange: Observable =
this._getLazyEmitter('onApiChange');
+ /** Emits when the playback quality has changed. */
@Output() readonly playbackQualityChange: Observable =
this._getLazyEmitter('onPlaybackQualityChange');
+ /** Emits when the playback rate has changed. */
@Output() readonly playbackRateChange: Observable =
this._getLazyEmitter('onPlaybackRateChange');
@@ -575,7 +583,7 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
}),
);
- const whenReady = () => {
+ const whenReady = (event: YT.PlayerEvent) => {
// Only assign the player once it's ready, otherwise YouTube doesn't expose some APIs.
this._ngZone.run(() => {
this._isLoading = false;
@@ -584,6 +592,7 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
this._pendingPlayer = undefined;
player.removeEventListener('onReady', whenReady);
this._playerChanges.next(player);
+ (this.ready as EventEmitter).emit(event);
this._setSize();
this._setQuality();
@@ -597,6 +606,12 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
const state = player.getPlayerState();
if (state === PlayerState.UNSTARTED || state === PlayerState.CUED || state == null) {
this._cuePlayer();
+ } else if (playVideo && this.startSeconds && this.startSeconds > 0) {
+ // We have to use `seekTo` when `startSeconds` are specified to simulate it playing from
+ // a specific time. The "proper" way to do it would be to either go through `cueVideoById`
+ // or `playerVars.start`, but at the time of writing both end up resetting the video
+ // to the state as if the user hasn't interacted with it.
+ player.seekTo(this.startSeconds, true);
}
this._changeDetectorRef.markForCheck();
diff --git a/tools/public_api_guard/youtube-player/youtube-player.md b/tools/public_api_guard/youtube-player/youtube-player.md
index fb8690f358f4..236abe369746 100644
--- a/tools/public_api_guard/youtube-player/youtube-player.md
+++ b/tools/public_api_guard/youtube-player/youtube-player.md
@@ -24,12 +24,10 @@ export const YOUTUBE_PLAYER_CONFIG: InjectionToken;
// @public
export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
constructor(...args: unknown[]);
- // (undocumented)
readonly apiChange: Observable;
disableCookies: boolean;
disablePlaceholder: boolean;
endSeconds: number | undefined;
- // (undocumented)
readonly error: Observable;
getAvailablePlaybackRates(): number[];
getAvailableQualityLevels(): YT.SuggestedVideoQuality[];
@@ -77,9 +75,7 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
pauseVideo(): void;
placeholderButtonLabel: string;
placeholderImageQuality: PlaceholderImageQuality;
- // (undocumented)
readonly playbackQualityChange: Observable;
- // (undocumented)
readonly playbackRateChange: Observable;
playerVars: YT.PlayerVars | undefined;
playVideo(): void;
@@ -90,7 +86,6 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
protected _shouldShowPlaceholder(): boolean;
showBeforeIframeApiLoads: boolean;
startSeconds: number | undefined;
- // (undocumented)
readonly stateChange: Observable;
stopVideo(): void;
suggestedQuality: YT.SuggestedVideoQuality | undefined;