diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts
index 88fc3e8e252b..534586867a9d 100644
--- a/src/youtube-player/youtube-player.ts
+++ b/src/youtube-player/youtube-player.ts
@@ -10,53 +10,24 @@
///
import {
- AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
NgZone,
OnDestroy,
- OnInit,
Output,
ViewChild,
ViewEncapsulation,
Inject,
PLATFORM_ID,
+ OnChanges,
+ SimpleChanges,
+ AfterViewInit,
} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
-
-import {
- combineLatest,
- ConnectableObservable,
- merge,
- MonoTypeOperatorFunction,
- Observable,
- of as observableOf,
- OperatorFunction,
- pipe,
- Subject,
- of,
- BehaviorSubject,
- fromEventPattern,
-} from 'rxjs';
-
-import {
- combineLatest as combineLatestOp,
- distinctUntilChanged,
- filter,
- map,
- publish,
- scan,
- skipWhile,
- startWith,
- take,
- takeUntil,
- withLatestFrom,
- switchMap,
- tap,
- mergeMap,
-} from 'rxjs/operators';
+import {Observable, of as observableOf, Subject, BehaviorSubject, fromEventPattern} from 'rxjs';
+import {takeUntil, switchMap} from 'rxjs/operators';
declare global {
interface Window {
@@ -68,21 +39,6 @@ declare global {
export const DEFAULT_PLAYER_WIDTH = 640;
export const DEFAULT_PLAYER_HEIGHT = 390;
-// The native YT.Player doesn't expose the set videoId, but we need it for
-// convenience.
-interface Player extends YT.Player {
- videoId?: string;
- playerVars?: YT.PlayerVars;
- host?: string;
-}
-
-// The player isn't fully initialized when it's constructed.
-// The only field available is destroy and addEventListener.
-type UninitializedPlayer = Pick<
- Player,
- 'videoId' | 'playerVars' | 'destroy' | 'addEventListener' | 'host'
->;
-
/**
* Object used to store the state of the player if the
* user tries to interact with the API before it has been loaded.
@@ -107,91 +63,62 @@ interface PendingPlayerState {
// This div is *replaced* by the YouTube player embed.
template: '
',
})
-export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
+export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
/** Whether we're currently rendering inside a browser. */
- private _isBrowser: boolean;
- private readonly _youtubeContainer = new Subject();
- private readonly _destroyed = new Subject();
- private _player: Player | undefined;
+ private readonly _isBrowser: boolean;
+ private _player: YT.Player | undefined;
+ private _pendingPlayer: YT.Player | undefined;
private _existingApiReadyCallback: (() => void) | undefined;
private _pendingPlayerState: PendingPlayerState | undefined;
- private readonly _playerChanges = new BehaviorSubject(undefined);
+ private readonly _destroyed = new Subject();
+ private readonly _playerChanges = new BehaviorSubject(undefined);
/** YouTube Video ID to view */
@Input()
- get videoId(): string | undefined {
- return this._videoId.value;
- }
- set videoId(videoId: string | undefined) {
- this._videoId.next(videoId);
- }
- private readonly _videoId = new BehaviorSubject(undefined);
+ videoId: string | undefined;
/** Height of video player */
@Input()
- get height(): number | undefined {
- return this._height.value;
+ get height(): number {
+ return this._height;
}
set height(height: number | undefined) {
- this._height.next(height || DEFAULT_PLAYER_HEIGHT);
+ this._height = height || DEFAULT_PLAYER_HEIGHT;
}
- private readonly _height = new BehaviorSubject(DEFAULT_PLAYER_HEIGHT);
+ private _height = DEFAULT_PLAYER_HEIGHT;
/** Width of video player */
@Input()
- get width(): number | undefined {
- return this._width.value;
+ get width(): number {
+ return this._width;
}
set width(width: number | undefined) {
- this._width.next(width || DEFAULT_PLAYER_WIDTH);
+ this._width = width || DEFAULT_PLAYER_WIDTH;
}
- private readonly _width = new BehaviorSubject(DEFAULT_PLAYER_WIDTH);
+ private _width = DEFAULT_PLAYER_WIDTH;
/** The moment when the player is supposed to start playing */
@Input()
- set startSeconds(startSeconds: number | undefined) {
- this._startSeconds.next(startSeconds);
- }
- private readonly _startSeconds = new BehaviorSubject(undefined);
+ startSeconds: number | undefined;
/** The moment when the player is supposed to stop playing */
@Input()
- set endSeconds(endSeconds: number | undefined) {
- this._endSeconds.next(endSeconds);
- }
- private readonly _endSeconds = new BehaviorSubject(undefined);
+ endSeconds: number | undefined;
/** The suggested quality of the player */
@Input()
- set suggestedQuality(suggestedQuality: YT.SuggestedVideoQuality | undefined) {
- this._suggestedQuality.next(suggestedQuality);
- }
- private readonly _suggestedQuality = new BehaviorSubject(
- undefined,
- );
+ suggestedQuality: YT.SuggestedVideoQuality | undefined;
/**
* Extra parameters used to configure the player. See:
* https://developers.google.com/youtube/player_parameters.html?playerVersion=HTML5#Parameters
*/
@Input()
- get playerVars(): YT.PlayerVars | undefined {
- return this._playerVars.value;
- }
- set playerVars(playerVars: YT.PlayerVars | undefined) {
- this._playerVars.next(playerVars);
- }
- private _playerVars = new BehaviorSubject(undefined);
+ playerVars: YT.PlayerVars | undefined;
/** Whether cookies inside the player have been disabled. */
@Input()
- get disableCookies(): boolean {
- return this._disableCookies.value;
- }
- set disableCookies(value: unknown) {
- this._disableCookies.next(!!value);
- }
- private readonly _disableCookies = new BehaviorSubject(false);
+ disableCookies: boolean = false;
/**
* Whether the iframe will attempt to load regardless of the status of the api on the
@@ -220,20 +147,22 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
this._getLazyEmitter('onPlaybackRateChange');
/** The element that will be replaced by the iframe. */
- @ViewChild('youtubeContainer')
+ @ViewChild('youtubeContainer', {static: true})
youtubeContainer: ElementRef;
- constructor(private _ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {
+ constructor(
+ private _ngZone: NgZone,
+ @Inject(PLATFORM_ID) platformId: Object,
+ ) {
this._isBrowser = isPlatformBrowser(platformId);
}
- ngOnInit() {
+ ngAfterViewInit() {
// Don't do anything if we're not in a browser environment.
if (!this._isBrowser) {
return;
}
- let iframeApiAvailableObs: Observable = observableOf(true);
if (!window.YT || !window.YT.Player) {
if (this.showBeforeIframeApiLoads && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw new Error(
@@ -243,95 +172,44 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
);
}
- const iframeApiAvailableSubject = new Subject();
this._existingApiReadyCallback = window.onYouTubeIframeAPIReady;
window.onYouTubeIframeAPIReady = () => {
- if (this._existingApiReadyCallback) {
- this._existingApiReadyCallback();
- }
- this._ngZone.run(() => iframeApiAvailableSubject.next(true));
+ this._existingApiReadyCallback?.();
+ this._ngZone.run(() => this._createPlayer());
};
- iframeApiAvailableObs = iframeApiAvailableSubject.pipe(take(1), startWith(false));
+ } else {
+ this._createPlayer();
}
+ }
- const hostObservable = this._disableCookies.pipe(
- map(cookiesDisabled => (cookiesDisabled ? 'https://www.youtube-nocookie.com' : undefined)),
- );
-
- // An observable of the currently loaded player.
- const playerObs = createPlayerObservable(
- this._youtubeContainer,
- this._videoId,
- hostObservable,
- iframeApiAvailableObs,
- this._width,
- this._height,
- this._playerVars,
- this._ngZone,
- ).pipe(
- tap(player => {
- // Emit this before the `waitUntilReady` call so that we can bind to
- // events that happen as the player is being initialized (e.g. `onReady`).
- this._playerChanges.next(player);
- }),
- waitUntilReady(player => {
- // Destroy the player if loading was aborted so that we don't end up leaking memory.
- if (!playerIsReady(player)) {
- player.destroy();
- }
- }),
- takeUntil(this._destroyed),
- publish(),
- );
-
- // Set up side effects to bind inputs to the player.
- playerObs.subscribe(player => {
- this._player = player;
-
- if (player && this._pendingPlayerState) {
- this._initializePlayer(player, this._pendingPlayerState);
+ ngOnChanges(changes: SimpleChanges): void {
+ if (this._shouldRecreatePlayer(changes)) {
+ this._createPlayer();
+ } else if (this._player) {
+ if (changes['width'] || changes['height']) {
+ this._setSize();
}
- this._pendingPlayerState = undefined;
- });
-
- bindSizeToPlayer(playerObs, this._width, this._height);
-
- bindSuggestedQualityToPlayer(playerObs, this._suggestedQuality);
-
- bindCueVideoCall(
- playerObs,
- this._videoId,
- this._startSeconds,
- this._endSeconds,
- this._suggestedQuality,
- this._destroyed,
- );
-
- // After all of the subscriptions are set up, connect the observable.
- (playerObs as ConnectableObservable).connect();
- }
+ if (changes['suggestedQuality']) {
+ this._setQuality();
+ }
- ngAfterViewInit() {
- this._youtubeContainer.next(this.youtubeContainer.nativeElement);
+ if (changes['startSeconds'] || changes['endSeconds'] || changes['suggestedQuality']) {
+ this._cuePlayer();
+ }
+ }
}
ngOnDestroy() {
+ this._pendingPlayer?.destroy();
+
if (this._player) {
this._player.destroy();
window.onYouTubeIframeAPIReady = this._existingApiReadyCallback;
}
this._playerChanges.complete();
- this._videoId.complete();
- this._height.complete();
- this._width.complete();
- this._startSeconds.complete();
- this._endSeconds.complete();
- this._suggestedQuality.complete();
- this._youtubeContainer.complete();
- this._playerVars.complete();
this._destroyed.next();
this._destroyed.complete();
}
@@ -522,9 +400,68 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
return this._pendingPlayerState;
}
- /** Initializes a player from a temporary state. */
- private _initializePlayer(player: YT.Player, state: PendingPlayerState): void {
- const {playbackState, playbackRate, volume, muted, seek} = state;
+ /**
+ * Determines whether a change in the component state
+ * requires the YouTube player to be recreated.
+ */
+ private _shouldRecreatePlayer(changes: SimpleChanges): boolean {
+ const change = changes['videoId'] || changes['playerVars'] || changes['disableCookies'];
+ return !!change && !change.isFirstChange();
+ }
+
+ /** Creates a new YouTube player and destroys the existing one. */
+ private _createPlayer() {
+ this._player?.destroy();
+ this._pendingPlayer?.destroy();
+
+ // A player can't be created if the API isn't loaded,
+ // or there isn't a video or playlist to be played.
+ if (typeof YT === 'undefined' || (!this.videoId && !this.playerVars?.list)) {
+ return;
+ }
+
+ // 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 player = this._ngZone.runOutsideAngular(
+ () =>
+ new YT.Player(this.youtubeContainer.nativeElement, {
+ videoId: this.videoId,
+ host: this.disableCookies ? 'https://www.youtube-nocookie.com' : undefined,
+ width: this.width,
+ height: this.height,
+ playerVars: this.playerVars,
+ }),
+ );
+
+ const whenReady = () => {
+ // Only assign the player once it's ready, otherwise YouTube doesn't expose some APIs.
+ this._player = player;
+ this._pendingPlayer = undefined;
+ player.removeEventListener('onReady', whenReady);
+ this._playerChanges.next(player);
+ this._setSize();
+ this._setQuality();
+
+ if (this._pendingPlayerState) {
+ this._applyPendingPlayerState(player, this._pendingPlayerState);
+ this._pendingPlayerState = undefined;
+ }
+
+ // Only cue the player when it either hasn't started yet or it's cued,
+ // otherwise cuing it can interrupt a player with autoplay enabled.
+ const state = player.getPlayerState();
+ if (state === YT.PlayerState.UNSTARTED || state === YT.PlayerState.CUED || state == null) {
+ this._cuePlayer();
+ }
+ };
+
+ this._pendingPlayer = player;
+ player.addEventListener('onReady', whenReady);
+ }
+
+ /** Applies any state that changed before the player was initialized. */
+ private _applyPendingPlayerState(player: YT.Player, pendingState: PendingPlayerState): void {
+ const {playbackState, playbackRate, volume, muted, seek} = pendingState;
switch (playbackState) {
case YT.PlayerState.PLAYING:
@@ -555,6 +492,30 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
}
}
+ /** Cues the player based on the current component state. */
+ private _cuePlayer() {
+ if (this._player && this.videoId) {
+ this._player.cueVideoById({
+ videoId: this.videoId,
+ startSeconds: this.startSeconds,
+ endSeconds: this.endSeconds,
+ suggestedQuality: this.suggestedQuality,
+ });
+ }
+ }
+
+ /** Sets the player's size based on the current input values. */
+ private _setSize() {
+ this._player?.setSize(this.width, this.height);
+ }
+
+ /** Sets the player's quality based on the current input values. */
+ private _setQuality() {
+ if (this._player && this.suggestedQuality) {
+ this._player.setPlaybackQuality(this.suggestedQuality);
+ }
+ }
+
/** Gets an observable that adds an event listener to the player when a user subscribes to it. */
private _getLazyEmitter(name: keyof YT.Events): Observable {
// Start with the stream of players. This way the events will be transferred
@@ -573,9 +534,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
// expose whether the player has been destroyed so we have to wrap it in a try/catch to
// prevent the entire stream from erroring out.
try {
- if ((player as Player).removeEventListener!) {
- (player as Player).removeEventListener(name, listener);
- }
+ player?.removeEventListener?.(name, listener);
} catch {}
},
)
@@ -583,7 +542,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
}),
// By default we run all the API interactions outside the zone
// so we have to bring the events back in manually when they emit.
- (source: Observable) =>
+ source =>
new Observable(observer =>
source.subscribe({
next: value => this._ngZone.run(() => observer.next(value)),
@@ -596,222 +555,3 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
);
}
}
-
-/** Listens to changes to the given width and height and sets it on the player. */
-function bindSizeToPlayer(
- playerObs: Observable,
- widthObs: Observable,
- heightObs: Observable,
-) {
- return combineLatest([playerObs, widthObs, heightObs]).subscribe(
- ([player, width, height]) => player && player.setSize(width, height),
- );
-}
-
-/** Listens to changes from the suggested quality and sets it on the given player. */
-function bindSuggestedQualityToPlayer(
- playerObs: Observable,
- suggestedQualityObs: Observable,
-) {
- return combineLatest([playerObs, suggestedQualityObs]).subscribe(
- ([player, suggestedQuality]) =>
- player && suggestedQuality && player.setPlaybackQuality(suggestedQuality),
- );
-}
-
-/**
- * Returns an observable that emits the loaded player once it's ready. Certain properties/methods
- * won't be available until the iframe finishes loading.
- * @param onAbort Callback function that will be invoked if the player loading was aborted before
- * it was able to complete. Can be used to clean up any loose references.
- */
-function waitUntilReady(
- onAbort: (player: UninitializedPlayer) => void,
-): OperatorFunction {
- return mergeMap(player => {
- if (!player) {
- return observableOf(undefined);
- }
- if (playerIsReady(player)) {
- return observableOf(player as Player);
- }
-
- // Since removeEventListener is not on Player when it's initialized, we can't use fromEvent.
- // The player is not initialized fully until the ready is called.
- return new Observable(emitter => {
- let aborted = false;
- let resolved = false;
- const onReady = (event: YT.PlayerEvent) => {
- resolved = true;
-
- if (!aborted) {
- event.target.removeEventListener('onReady', onReady);
- emitter.next(event.target);
- }
- };
-
- player.addEventListener('onReady', onReady);
-
- return () => {
- aborted = true;
-
- if (!resolved) {
- onAbort(player);
- }
- };
- }).pipe(take(1), startWith(undefined));
- });
-}
-
-/** Create an observable for the player based on the given options. */
-function createPlayerObservable(
- youtubeContainer: Observable,
- videoIdObs: Observable,
- hostObs: Observable,
- iframeApiAvailableObs: Observable,
- widthObs: Observable,
- heightObs: Observable,
- playerVarsObs: Observable,
- ngZone: NgZone,
-): Observable {
- const playerOptions = combineLatest([videoIdObs, hostObs, playerVarsObs]).pipe(
- withLatestFrom(combineLatest([widthObs, heightObs])),
- map(([constructorOptions, sizeOptions]) => {
- const [videoId, host, playerVars] = constructorOptions;
- const [width, height] = sizeOptions;
-
- // If there's no video id or a list isn't supplied, bail out
- if (!videoId && !(playerVars?.list && playerVars?.listType)) {
- return undefined;
- }
-
- return {videoId, playerVars, width, height, host};
- }),
- );
-
- return combineLatest([youtubeContainer, playerOptions, of(ngZone)]).pipe(
- skipUntilRememberLatest(iframeApiAvailableObs),
- scan(syncPlayerState, undefined),
- distinctUntilChanged(),
- );
-}
-
-/** Skips the given observable until the other observable emits true, then emit the latest. */
-function skipUntilRememberLatest(notifier: Observable): MonoTypeOperatorFunction {
- return pipe(
- combineLatestOp(notifier),
- skipWhile(([_, doneSkipping]) => !doneSkipping),
- map(([value]) => value),
- );
-}
-
-/** Destroy the player if there are no options, or create the player if there are options. */
-function syncPlayerState(
- player: UninitializedPlayer | undefined,
- [container, videoOptions, ngZone]: [HTMLElement, YT.PlayerOptions | undefined, NgZone],
-): UninitializedPlayer | undefined {
- if (
- player &&
- videoOptions &&
- (player.playerVars !== videoOptions.playerVars || player.host !== videoOptions.host)
- ) {
- // The player needs to be recreated if the playerVars are different.
- player.destroy();
- } else if (!videoOptions) {
- if (player) {
- // Destroy the player if the videoId was removed.
- player.destroy();
- }
- return;
- } else if (player) {
- return player;
- }
-
- // 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),
- );
- newPlayer.videoId = videoOptions.videoId;
- newPlayer.playerVars = videoOptions.playerVars;
- newPlayer.host = videoOptions.host;
- return newPlayer;
-}
-
-/**
- * Call cueVideoById if the videoId changes, or when start or end seconds change. cueVideoById will
- * change the loaded video id to the given videoId, and set the start and end times to the given
- * start/end seconds.
- */
-function bindCueVideoCall(
- playerObs: Observable,
- videoIdObs: Observable,
- startSecondsObs: Observable,
- endSecondsObs: Observable,
- suggestedQualityObs: Observable,
- destroyed: Observable,
-) {
- const cueOptionsObs = combineLatest([startSecondsObs, endSecondsObs]).pipe(
- map(([startSeconds, endSeconds]) => ({startSeconds, endSeconds})),
- );
-
- // Only respond to changes in cue options if the player is not running.
- const filteredCueOptions = cueOptionsObs.pipe(
- filterOnOther(playerObs, player => !!player && !hasPlayerStarted(player)),
- );
-
- // If the video id changed, there's no reason to run 'cue' unless the player
- // was initialized with a different video id.
- const changedVideoId = videoIdObs.pipe(
- filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId),
- );
-
- // If the player changed, there's no reason to run 'cue' unless there are cue options.
- const changedPlayer = playerObs.pipe(
- filterOnOther(
- combineLatest([videoIdObs, cueOptionsObs]),
- ([videoId, cueOptions], player) =>
- !!player &&
- (videoId != player.videoId || !!cueOptions.startSeconds || !!cueOptions.endSeconds),
- ),
- );
-
- merge(changedPlayer, changedVideoId, filteredCueOptions)
- .pipe(
- withLatestFrom(combineLatest([playerObs, videoIdObs, cueOptionsObs, suggestedQualityObs])),
- map(([_, values]) => values),
- takeUntil(destroyed),
- )
- .subscribe(([player, videoId, cueOptions, suggestedQuality]) => {
- if (!videoId || !player) {
- return;
- }
- player.videoId = videoId;
- player.cueVideoById({
- videoId,
- suggestedQuality,
- ...cueOptions,
- });
- });
-}
-
-function hasPlayerStarted(player: YT.Player): boolean {
- const state = player.getPlayerState();
- return state !== YT.PlayerState.UNSTARTED && state !== YT.PlayerState.CUED;
-}
-
-function playerIsReady(player: UninitializedPlayer): player is Player {
- return 'getPlayerStatus' in player;
-}
-
-/** Combines the two observables temporarily for the filter function. */
-function filterOnOther(
- otherObs: Observable,
- filterFn: (t: T, r?: R) => boolean,
-): MonoTypeOperatorFunction {
- return pipe(
- withLatestFrom(otherObs),
- filter(([value, other]) => filterFn(other, value)),
- map(([value]) => value),
- );
-}
diff --git a/tools/public_api_guard/youtube-player/youtube-player.md b/tools/public_api_guard/youtube-player/youtube-player.md
index 6b0ec32047a0..1d29317416f1 100644
--- a/tools/public_api_guard/youtube-player/youtube-player.md
+++ b/tools/public_api_guard/youtube-player/youtube-player.md
@@ -11,17 +11,17 @@ import { ElementRef } from '@angular/core';
import * as i0 from '@angular/core';
import { NgZone } from '@angular/core';
import { Observable } from 'rxjs';
+import { OnChanges } from '@angular/core';
import { OnDestroy } from '@angular/core';
-import { OnInit } from '@angular/core';
+import { SimpleChanges } from '@angular/core';
// @public
-export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
+export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy {
constructor(_ngZone: NgZone, platformId: Object);
// (undocumented)
readonly apiChange: Observable;
- get disableCookies(): boolean;
- set disableCookies(value: unknown);
- set endSeconds(endSeconds: number | undefined);
+ disableCookies: boolean;
+ endSeconds: number | undefined;
// (undocumented)
readonly error: Observable;
getAvailablePlaybackRates(): number[];
@@ -35,38 +35,36 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
getVideoLoadedFraction(): number;
getVideoUrl(): string;
getVolume(): number;
- get height(): number | undefined;
+ get height(): number;
set height(height: number | undefined);
isMuted(): boolean;
mute(): void;
// (undocumented)
ngAfterViewInit(): void;
// (undocumented)
- ngOnDestroy(): void;
+ ngOnChanges(changes: SimpleChanges): void;
// (undocumented)
- ngOnInit(): void;
+ ngOnDestroy(): void;
pauseVideo(): void;
// (undocumented)
readonly playbackQualityChange: Observable;
// (undocumented)
readonly playbackRateChange: Observable;
- get playerVars(): YT.PlayerVars | undefined;
- set playerVars(playerVars: YT.PlayerVars | undefined);
+ playerVars: YT.PlayerVars | undefined;
playVideo(): void;
readonly ready: Observable;
seekTo(seconds: number, allowSeekAhead: boolean): void;
setPlaybackRate(playbackRate: number): void;
setVolume(volume: number): void;
showBeforeIframeApiLoads: boolean | undefined;
- set startSeconds(startSeconds: number | undefined);
+ startSeconds: number | undefined;
// (undocumented)
readonly stateChange: Observable;
stopVideo(): void;
- set suggestedQuality(suggestedQuality: YT.SuggestedVideoQuality | undefined);
+ suggestedQuality: YT.SuggestedVideoQuality | undefined;
unMute(): void;
- get videoId(): string | undefined;
- set videoId(videoId: string | undefined);
- get width(): number | undefined;
+ videoId: string | undefined;
+ get width(): number;
set width(width: number | undefined);
youtubeContainer: ElementRef;
// (undocumented)