Skip to content

Commit cc36af7

Browse files
authored
feat(youtube-player): support passing in the playerVars parameter (#19746)
We tried doing this before in #17672, but we never managed to wrap it up. These changes add support for passing in the `playerVars` parameter to the YouTube API which has some settings like autoplay and hiding the video controls. Fixes #19267.
1 parent 970d15b commit cc36af7

File tree

3 files changed

+53
-13
lines changed

3 files changed

+53
-13
lines changed

src/youtube-player/youtube-player.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('YoutubePlayer', () => {
4949
videoId: VIDEO_ID,
5050
width: DEFAULT_PLAYER_WIDTH,
5151
height: DEFAULT_PLAYER_HEIGHT,
52+
playerVars: undefined
5253
}));
5354
});
5455

@@ -131,6 +132,21 @@ describe('YoutubePlayer', () => {
131132
expect(testComponent.youtubePlayer.height).toBe(DEFAULT_PLAYER_HEIGHT);
132133
});
133134

135+
it('passes the configured playerVars to the player', () => {
136+
const playerVars: YT.PlayerVars = { modestbranding: YT.ModestBranding.Modest };
137+
fixture.componentInstance.playerVars = playerVars;
138+
fixture.detectChanges();
139+
140+
events.onReady({target: playerSpy});
141+
const calls = playerCtorSpy.calls.all();
142+
143+
// We expect 2 calls since the first one is run on init and the
144+
// second one happens after the `playerVars` have changed.
145+
expect(calls.length).toBe(2);
146+
expect(calls[0].args[1]).toEqual(jasmine.objectContaining({playerVars: undefined}));
147+
expect(calls[1].args[1]).toEqual(jasmine.objectContaining({playerVars}));
148+
});
149+
134150
it('initializes the player with start and end seconds', () => {
135151
testComponent.startSeconds = 5;
136152
testComponent.endSeconds = 6;
@@ -462,6 +478,7 @@ describe('YoutubePlayer', () => {
462478
template: `
463479
<youtube-player #player [videoId]="videoId" *ngIf="visible" [width]="width" [height]="height"
464480
[startSeconds]="startSeconds" [endSeconds]="endSeconds" [suggestedQuality]="suggestedQuality"
481+
[playerVars]="playerVars"
465482
(ready)="onReady($event)"
466483
(stateChange)="onStateChange($event)"
467484
(playbackQualityChange)="onPlaybackQualityChange($event)"
@@ -479,6 +496,7 @@ class TestApp {
479496
startSeconds: number | undefined;
480497
endSeconds: number | undefined;
481498
suggestedQuality: YT.SuggestedVideoQuality | undefined;
499+
playerVars: YT.PlayerVars | undefined;
482500
onReady = jasmine.createSpy('onReady');
483501
onStateChange = jasmine.createSpy('onStateChange');
484502
onPlaybackQualityChange = jasmine.createSpy('onPlaybackQualityChange');

src/youtube-player/youtube-player.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,13 @@ export const DEFAULT_PLAYER_HEIGHT = 390;
7171
// The native YT.Player doesn't expose the set videoId, but we need it for
7272
// convenience.
7373
interface Player extends YT.Player {
74-
videoId?: string | undefined;
74+
videoId?: string;
75+
playerVars?: YT.PlayerVars;
7576
}
7677

7778
// The player isn't fully initialized when it's constructed.
7879
// The only field available is destroy and addEventListener.
79-
type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListener'>;
80+
type UninitializedPlayer = Pick<Player, 'videoId' | 'playerVars' | 'destroy' | 'addEventListener'>;
8081

8182
/**
8283
* Object used to store the state of the player if the
@@ -157,6 +158,17 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
157158
}
158159
private _suggestedQuality = new BehaviorSubject<YT.SuggestedVideoQuality | undefined>(undefined);
159160

161+
/**
162+
* Extra parameters used to configure the player. See:
163+
* https://developers.google.com/youtube/player_parameters.html?playerVersion=HTML5#Parameters
164+
*/
165+
@Input()
166+
get playerVars(): YT.PlayerVars | undefined { return this._playerVars.value; }
167+
set playerVars(playerVars: YT.PlayerVars | undefined) {
168+
this._playerVars.next(playerVars);
169+
}
170+
private _playerVars = new BehaviorSubject<YT.PlayerVars | undefined>(undefined);
171+
160172
/**
161173
* Whether the iframe will attempt to load regardless of the status of the api on the
162174
* page. Set this to true if you don't want the `onYouTubeIframeAPIReady` field to be
@@ -225,6 +237,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
225237
iframeApiAvailableObs,
226238
this._width,
227239
this._height,
240+
this._playerVars,
228241
this._ngZone
229242
).pipe(tap(player => {
230243
// Emit this before the `waitUntilReady` call so that we can bind to
@@ -290,6 +303,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
290303
this._endSeconds.complete();
291304
this._suggestedQuality.complete();
292305
this._youtubeContainer.complete();
306+
this._playerVars.complete();
293307
this._destroyed.next();
294308
this._destroyed.complete();
295309
}
@@ -614,15 +628,18 @@ function createPlayerObservable(
614628
iframeApiAvailableObs: Observable<boolean>,
615629
widthObs: Observable<number>,
616630
heightObs: Observable<number>,
631+
playerVarsObs: Observable<YT.PlayerVars | undefined>,
617632
ngZone: NgZone
618633
): Observable<UninitializedPlayer | undefined> {
619634

620-
const playerOptions =
621-
videoIdObs
622-
.pipe(
623-
withLatestFrom(combineLatest([widthObs, heightObs])),
624-
map(([videoId, [width, height]]) => videoId ? ({videoId, width, height}) : undefined),
625-
);
635+
const playerOptions = combineLatest([videoIdObs, playerVarsObs]).pipe(
636+
withLatestFrom(combineLatest([widthObs, heightObs])),
637+
map(([constructorOptions, sizeOptions]) => {
638+
const [videoId, playerVars] = constructorOptions;
639+
const [width, height] = sizeOptions;
640+
return videoId ? ({ videoId, playerVars, width, height }) : undefined;
641+
}),
642+
);
626643

627644
return combineLatest([youtubeContainer, playerOptions, of(ngZone)])
628645
.pipe(
@@ -644,22 +661,25 @@ function syncPlayerState(
644661
player: UninitializedPlayer | undefined,
645662
[container, videoOptions, ngZone]: [HTMLElement, YT.PlayerOptions | undefined, NgZone],
646663
): UninitializedPlayer | undefined {
647-
if (!videoOptions) {
664+
if (player && videoOptions && player.playerVars !== videoOptions.playerVars) {
665+
// The player needs to be recreated if the playerVars are different.
666+
player.destroy();
667+
} else if (!videoOptions) {
648668
if (player) {
669+
// Destroy the player if the videoId was removed.
649670
player.destroy();
650671
}
651672
return;
652-
}
653-
if (player) {
673+
} else if (player) {
654674
return player;
655675
}
656676

657677
// Important! We need to create the Player object outside of the `NgZone`, because it kicks
658678
// off a 250ms setInterval which will continually trigger change detection if we don't.
659679
const newPlayer: UninitializedPlayer =
660680
ngZone.runOutsideAngular(() => new YT.Player(container, videoOptions));
661-
// Bind videoId for future use.
662681
newPlayer.videoId = videoOptions.videoId;
682+
newPlayer.playerVars = videoOptions.playerVars;
663683
return newPlayer;
664684
}
665685

tools/public_api_guard/youtube-player/youtube-player.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export declare class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
66
set height(height: number | undefined);
77
playbackQualityChange: Observable<YT.OnPlaybackQualityChangeEvent>;
88
playbackRateChange: Observable<YT.OnPlaybackRateChangeEvent>;
9+
get playerVars(): YT.PlayerVars | undefined;
10+
set playerVars(playerVars: YT.PlayerVars | undefined);
911
ready: Observable<YT.PlayerEvent>;
1012
showBeforeIframeApiLoads: boolean | undefined;
1113
set startSeconds(startSeconds: number | undefined);
@@ -41,7 +43,7 @@ export declare class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
4143
setVolume(volume: number): void;
4244
stopVideo(): void;
4345
unMute(): void;
44-
static ɵcmp: i0.ɵɵComponentDefWithMeta<YouTubePlayer, "youtube-player", never, { "videoId": "videoId"; "height": "height"; "width": "width"; "startSeconds": "startSeconds"; "endSeconds": "endSeconds"; "suggestedQuality": "suggestedQuality"; "showBeforeIframeApiLoads": "showBeforeIframeApiLoads"; }, { "ready": "ready"; "stateChange": "stateChange"; "error": "error"; "apiChange": "apiChange"; "playbackQualityChange": "playbackQualityChange"; "playbackRateChange": "playbackRateChange"; }, never, never>;
46+
static ɵcmp: i0.ɵɵComponentDefWithMeta<YouTubePlayer, "youtube-player", never, { "videoId": "videoId"; "height": "height"; "width": "width"; "startSeconds": "startSeconds"; "endSeconds": "endSeconds"; "suggestedQuality": "suggestedQuality"; "playerVars": "playerVars"; "showBeforeIframeApiLoads": "showBeforeIframeApiLoads"; }, { "ready": "ready"; "stateChange": "stateChange"; "error": "error"; "apiChange": "apiChange"; "playbackQualityChange": "playbackQualityChange"; "playbackRateChange": "playbackRateChange"; }, never, never>;
4547
static ɵfac: i0.ɵɵFactoryDef<YouTubePlayer, never>;
4648
}
4749

0 commit comments

Comments
 (0)