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;