Skip to content

Commit f000b2f

Browse files
authored
feat(youtube-player): support no cookie mode (angular#25165)
Adds a `disableCookies` input that puts the player into "no cookie" mode. Fixes angular#19681.
1 parent ed825e6 commit f000b2f

File tree

6 files changed

+72
-8
lines changed

6 files changed

+72
-8
lines changed

src/dev-app/youtube-player/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ng_module(
1010
":youtube_player_demo_scss",
1111
],
1212
deps = [
13+
"//src/material/legacy-checkbox",
1314
"//src/material/legacy-radio",
1415
"//src/youtube-player",
1516
"@npm//@angular/forms",

src/dev-app/youtube-player/youtube-player-demo.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ <h1>Basic Example</h1>
1010
<mat-radio-button [value]="undefined">Unset</mat-radio-button>
1111
</mat-radio-group>
1212
</div>
13+
<div class="demo-video-selection">
14+
<mat-checkbox [(ngModel)]="disableCookies">Disable cookies</mat-checkbox>
15+
</div>
1316
<youtube-player [videoId]="selectedVideo && selectedVideo.id"
14-
[width]="videoWidth" [height]="videoHeight"></youtube-player>
17+
[width]="videoWidth" [height]="videoHeight"
18+
[disableCookies]="disableCookies"></youtube-player>
1519
</section>
1620
</div>

src/dev-app/youtube-player/youtube-player-demo.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {CommonModule} from '@angular/common';
1818
import {FormsModule} from '@angular/forms';
1919
import {MatLegacyRadioModule} from '@angular/material/legacy-radio';
2020
import {YouTubePlayerModule} from '@angular/youtube-player';
21+
import {MatLegacyCheckboxModule} from '@angular/material/legacy-checkbox';
2122

2223
interface Video {
2324
id: string;
@@ -44,14 +45,21 @@ const VIDEOS: Video[] = [
4445
templateUrl: 'youtube-player-demo.html',
4546
styleUrls: ['youtube-player-demo.css'],
4647
standalone: true,
47-
imports: [CommonModule, FormsModule, MatLegacyRadioModule, YouTubePlayerModule],
48+
imports: [
49+
CommonModule,
50+
FormsModule,
51+
MatLegacyRadioModule,
52+
MatLegacyCheckboxModule,
53+
YouTubePlayerModule,
54+
],
4855
})
4956
export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {
5057
@ViewChild('demoYouTubePlayer') demoYouTubePlayer: ElementRef<HTMLDivElement>;
5158
selectedVideo: Video | undefined = VIDEOS[0];
5259
videos = VIDEOS;
5360
videoWidth: number | undefined;
5461
videoHeight: number | undefined;
62+
disableCookies = false;
5563

5664
constructor(private _changeDetectorRef: ChangeDetectorRef) {
5765
this._loadApi();

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

+24
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,28 @@ describe('YoutubePlayer', () => {
380380

381381
expect(playerSpy.seekTo).toHaveBeenCalledWith(1337, true);
382382
});
383+
384+
it('should be able to disable cookies', () => {
385+
const containerElement = fixture.nativeElement.querySelector('div');
386+
387+
expect(playerCtorSpy).toHaveBeenCalledWith(
388+
containerElement,
389+
jasmine.objectContaining({
390+
host: undefined,
391+
}),
392+
);
393+
394+
playerCtorSpy.calls.reset();
395+
fixture.componentInstance.disableCookies = true;
396+
fixture.detectChanges();
397+
398+
expect(playerCtorSpy).toHaveBeenCalledWith(
399+
containerElement,
400+
jasmine.objectContaining({
401+
host: 'https://www.youtube-nocookie.com',
402+
}),
403+
);
404+
});
383405
});
384406

385407
describe('API loaded asynchronously', () => {
@@ -498,6 +520,7 @@ describe('YoutubePlayer', () => {
498520
<youtube-player #player [videoId]="videoId" *ngIf="visible" [width]="width" [height]="height"
499521
[startSeconds]="startSeconds" [endSeconds]="endSeconds" [suggestedQuality]="suggestedQuality"
500522
[playerVars]="playerVars"
523+
[disableCookies]="disableCookies"
501524
(ready)="onReady($event)"
502525
(stateChange)="onStateChange($event)"
503526
(playbackQualityChange)="onPlaybackQualityChange($event)"
@@ -509,6 +532,7 @@ describe('YoutubePlayer', () => {
509532
})
510533
class TestApp {
511534
videoId: string | undefined = VIDEO_ID;
535+
disableCookies = false;
512536
visible = true;
513537
width: number | undefined;
514538
height: number | undefined;

src/youtube-player/youtube-player.ts

+30-5
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,15 @@ export const DEFAULT_PLAYER_HEIGHT = 390;
7373
interface Player extends YT.Player {
7474
videoId?: string;
7575
playerVars?: YT.PlayerVars;
76+
host?: string;
7677
}
7778

7879
// The player isn't fully initialized when it's constructed.
7980
// The only field available is destroy and addEventListener.
80-
type UninitializedPlayer = Pick<Player, 'videoId' | 'playerVars' | 'destroy' | 'addEventListener'>;
81+
type UninitializedPlayer = Pick<
82+
Player,
83+
'videoId' | 'playerVars' | 'destroy' | 'addEventListener' | 'host'
84+
>;
8185

8286
/**
8387
* Object used to store the state of the player if the
@@ -179,6 +183,16 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
179183
}
180184
private _playerVars = new BehaviorSubject<YT.PlayerVars | undefined>(undefined);
181185

186+
/** Whether cookies inside the player have been disabled. */
187+
@Input()
188+
get disableCookies(): boolean {
189+
return this._disableCookies.value;
190+
}
191+
set disableCookies(value: unknown) {
192+
this._disableCookies.next(!!value);
193+
}
194+
private readonly _disableCookies = new BehaviorSubject<boolean>(false);
195+
182196
/**
183197
* Whether the iframe will attempt to load regardless of the status of the api on the
184198
* page. Set this to true if you don't want the `onYouTubeIframeAPIReady` field to be
@@ -241,10 +255,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
241255
iframeApiAvailableObs = iframeApiAvailableSubject.pipe(take(1), startWith(false));
242256
}
243257

258+
const hostObservable = this._disableCookies.pipe(
259+
map(cookiesDisabled => (cookiesDisabled ? 'https://www.youtube-nocookie.com' : undefined)),
260+
);
261+
244262
// An observable of the currently loaded player.
245263
const playerObs = createPlayerObservable(
246264
this._youtubeContainer,
247265
this._videoId,
266+
hostObservable,
248267
iframeApiAvailableObs,
249268
this._width,
250269
this._height,
@@ -648,18 +667,19 @@ function waitUntilReady(
648667
function createPlayerObservable(
649668
youtubeContainer: Observable<HTMLElement>,
650669
videoIdObs: Observable<string | undefined>,
670+
hostObs: Observable<string | undefined>,
651671
iframeApiAvailableObs: Observable<boolean>,
652672
widthObs: Observable<number>,
653673
heightObs: Observable<number>,
654674
playerVarsObs: Observable<YT.PlayerVars | undefined>,
655675
ngZone: NgZone,
656676
): Observable<UninitializedPlayer | undefined> {
657-
const playerOptions = combineLatest([videoIdObs, playerVarsObs]).pipe(
677+
const playerOptions = combineLatest([videoIdObs, hostObs, playerVarsObs]).pipe(
658678
withLatestFrom(combineLatest([widthObs, heightObs])),
659679
map(([constructorOptions, sizeOptions]) => {
660-
const [videoId, playerVars] = constructorOptions;
680+
const [videoId, host, playerVars] = constructorOptions;
661681
const [width, height] = sizeOptions;
662-
return videoId ? {videoId, playerVars, width, height} : undefined;
682+
return videoId ? {videoId, playerVars, width, height, host} : undefined;
663683
}),
664684
);
665685

@@ -684,7 +704,11 @@ function syncPlayerState(
684704
player: UninitializedPlayer | undefined,
685705
[container, videoOptions, ngZone]: [HTMLElement, YT.PlayerOptions | undefined, NgZone],
686706
): UninitializedPlayer | undefined {
687-
if (player && videoOptions && player.playerVars !== videoOptions.playerVars) {
707+
if (
708+
player &&
709+
videoOptions &&
710+
(player.playerVars !== videoOptions.playerVars || player.host !== videoOptions.host)
711+
) {
688712
// The player needs to be recreated if the playerVars are different.
689713
player.destroy();
690714
} else if (!videoOptions) {
@@ -704,6 +728,7 @@ function syncPlayerState(
704728
);
705729
newPlayer.videoId = videoOptions.videoId;
706730
newPlayer.playerVars = videoOptions.playerVars;
731+
newPlayer.host = videoOptions.host;
707732
return newPlayer;
708733
}
709734

tools/public_api_guard/youtube-player/youtube-player.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
2525
constructor(_ngZone: NgZone, platformId: Object);
2626
// (undocumented)
2727
readonly apiChange: Observable<YT.PlayerEvent>;
28+
get disableCookies(): boolean;
29+
set disableCookies(value: unknown);
2830
set endSeconds(endSeconds: number | undefined);
2931
// (undocumented)
3032
readonly error: Observable<YT.OnErrorEvent>;
@@ -74,7 +76,7 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
7476
set width(width: number | undefined);
7577
youtubeContainer: ElementRef<HTMLElement>;
7678
// (undocumented)
77-
static ɵcmp: i0.ɵɵComponentDeclaration<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, false>;
79+
static ɵcmp: i0.ɵɵComponentDeclaration<YouTubePlayer, "youtube-player", never, { "videoId": "videoId"; "height": "height"; "width": "width"; "startSeconds": "startSeconds"; "endSeconds": "endSeconds"; "suggestedQuality": "suggestedQuality"; "playerVars": "playerVars"; "disableCookies": "disableCookies"; "showBeforeIframeApiLoads": "showBeforeIframeApiLoads"; }, { "ready": "ready"; "stateChange": "stateChange"; "error": "error"; "apiChange": "apiChange"; "playbackQualityChange": "playbackQualityChange"; "playbackRateChange": "playbackRateChange"; }, never, never, false>;
7880
// (undocumented)
7981
static ɵfac: i0.ɵɵFactoryDeclaration<YouTubePlayer, never>;
8082
}

0 commit comments

Comments
 (0)