Skip to content

Commit 013f8a4

Browse files
jamesikanoscrisbeto
authored andcommitted
fix(youtube-player): Allow playlists without specifying videoId (#27529) (#27588)
* fix(youtube-player): Allow playlists without specifying videoId (#27529) When using a YouTube Player, it is possible to specify a playlist without specifying a videoId. This is useful when you want to play a playlist and don't have a VideoId. Previously, the player would not initialise if a VideoId wasn't specified but a Playlist was specified, even though the YouTube API supports this use case. This commit adds support for this use case. * refactor(youtube-player): fixed spelling mistake (cherry picked from commit d41be3d)
1 parent e8fb85b commit 013f8a4

File tree

4 files changed

+94
-3
lines changed

4 files changed

+94
-3
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ <h1>Basic Example</h1>
1313
<div class="demo-video-selection">
1414
<mat-checkbox [(ngModel)]="disableCookies">Disable cookies</mat-checkbox>
1515
</div>
16-
<youtube-player [videoId]="selectedVideo && selectedVideo.id"
16+
<youtube-player [videoId]="selectedVideoId"
17+
[playerVars]="playerVars"
1718
[width]="videoWidth" [height]="videoHeight"
1819
[disableCookies]="disableCookies"></youtube-player>
1920
</section>

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox';
2323
interface Video {
2424
id: string;
2525
name: string;
26+
isPlaylist?: boolean;
2627
}
2728

2829
const VIDEOS: Video[] = [
@@ -38,6 +39,16 @@ const VIDEOS: Video[] = [
3839
id: 'invalidname',
3940
name: 'Invalid',
4041
},
42+
{
43+
id: 'PLOa5YIicjJ-XCGXwnEmMmpHHCn11gUgvL',
44+
name: 'Angular Forms Playlist',
45+
isPlaylist: true,
46+
},
47+
{
48+
id: 'PLOa5YIicjJ-VpOOoLczAGTLEEznZ2JEa6',
49+
name: 'Angular Router Playlist',
50+
isPlaylist: true,
51+
},
4152
];
4253

4354
@Component({
@@ -49,14 +60,19 @@ const VIDEOS: Video[] = [
4960
})
5061
export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {
5162
@ViewChild('demoYouTubePlayer') demoYouTubePlayer: ElementRef<HTMLDivElement>;
52-
selectedVideo: Video | undefined = VIDEOS[0];
63+
private _selectedVideo?: Video;
64+
private _playerVars?: YT.PlayerVars;
65+
private _selectedVideoId?: string;
66+
5367
videos = VIDEOS;
5468
videoWidth: number | undefined;
5569
videoHeight: number | undefined;
5670
disableCookies = false;
5771

5872
constructor(private _changeDetectorRef: ChangeDetectorRef) {
5973
this._loadApi();
74+
75+
this.selectedVideo = VIDEOS[0];
6076
}
6177

6278
ngAfterViewInit(): void {
@@ -75,6 +91,37 @@ export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {
7591
window.removeEventListener('resize', this.onResize);
7692
}
7793

94+
get selectedVideoId() {
95+
return this._selectedVideoId;
96+
}
97+
98+
get playerVars() {
99+
return this._playerVars;
100+
}
101+
102+
get selectedVideo() {
103+
return this._selectedVideo;
104+
}
105+
106+
set selectedVideo(value: Video | undefined) {
107+
this._selectedVideo = value;
108+
109+
// If the video is a playlist, don't send a video id, and prepare playerVars instead
110+
111+
if (!value?.isPlaylist) {
112+
this._playerVars = undefined;
113+
this._selectedVideoId = value?.id;
114+
return;
115+
}
116+
117+
this._playerVars = {
118+
list: this._selectedVideo?.id,
119+
listType: 'playlist',
120+
};
121+
122+
this._selectedVideoId = undefined;
123+
}
124+
78125
private _loadApi() {
79126
if (!window.YT) {
80127
// We don't need to wait for the API to load since the

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,43 @@ describe('YoutubePlayer', () => {
402402
}),
403403
);
404404
});
405+
406+
it('should play with a playlist id instead of a video id', () => {
407+
playerCtorSpy.calls.reset();
408+
409+
const playerVars: YT.PlayerVars = {
410+
list: 'some-playlist-id',
411+
listType: 'playlist',
412+
};
413+
414+
testComponent.videoId = undefined;
415+
testComponent.playerVars = playerVars;
416+
417+
fixture.detectChanges();
418+
419+
let calls = playerCtorSpy.calls.all();
420+
421+
expect(calls.length).toBe(1);
422+
expect(calls[0].args[1]).toEqual(jasmine.objectContaining({playerVars, videoId: undefined}));
423+
424+
playerSpy.destroy.calls.reset();
425+
playerCtorSpy.calls.reset();
426+
427+
// Change the vars so that the list type is undefined
428+
// We only support a "list" if there's an accompanying "listType"
429+
testComponent.playerVars = {
430+
...playerVars,
431+
listType: undefined,
432+
};
433+
434+
fixture.detectChanges();
435+
436+
// The previous instance should have been destroyed
437+
expect(playerSpy.destroy).toHaveBeenCalled();
438+
439+
// Don't expect it to have been called
440+
expect(playerCtorSpy.calls.all().length).toHaveSize(0);
441+
});
405442
});
406443

407444
describe('API loaded asynchronously', () => {

src/youtube-player/youtube-player.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,13 @@ function createPlayerObservable(
679679
map(([constructorOptions, sizeOptions]) => {
680680
const [videoId, host, playerVars] = constructorOptions;
681681
const [width, height] = sizeOptions;
682-
return videoId ? {videoId, playerVars, width, height, host} : undefined;
682+
683+
// If there's no video id or a list isn't supplied, bail out
684+
if (!videoId && !(playerVars?.list && playerVars?.listType)) {
685+
return undefined;
686+
}
687+
688+
return {videoId, playerVars, width, height, host};
683689
}),
684690
);
685691

0 commit comments

Comments
 (0)