Skip to content

Commit 59b0ee4

Browse files
crisbetojelbourn
authored andcommitted
fix(youtube-player): errors during server-side rendering (#17091)
Fixes the `youtube-player` component throwing errors if it's rendered on the server. Also does some minor code cleanup.
1 parent d612019 commit 59b0ee4

File tree

6 files changed

+57
-87
lines changed

6 files changed

+57
-87
lines changed

src/universal-app/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ng_module(
1818
],
1919
deps = [
2020
"@npm//@angular/platform-server",
21+
"//src/youtube-player",
2122
] + CDK_TARGETS + CDK_EXPERIMENTAL_TARGETS + MATERIAL_TARGETS + MATERIAL_EXPERIMENTAL_TARGETS,
2223
)
2324

src/universal-app/kitchen-sink/kitchen-sink.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,7 @@ <h2>Virtual scroll</h2>
379379
Item #{{i}}
380380
</div>
381381
</cdk-virtual-scroll-viewport>
382+
383+
<h2>YouTube player</h2>
384+
385+
<youtube-player videoId="dQw4w9WgXcQ"></youtube-player>

src/universal-app/kitchen-sink/kitchen-sink.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {MatDividerModule} from '@angular/material/divider';
3737
import {MatFormFieldModule} from '@angular/material/form-field';
3838
import {MatSortModule} from '@angular/material/sort';
3939
import {MatStepperModule} from '@angular/material/stepper';
40+
import {YouTubePlayerModule} from '@angular/youtube-player';
4041
import {Observable, of as observableOf} from 'rxjs';
4142

4243
export class TableDataSource extends DataSource<any> {
@@ -136,6 +137,9 @@ export class KitchenSink {
136137
// CDK Modules
137138
CdkTableModule,
138139
DragDropModule,
140+
141+
// Other modules
142+
YouTubePlayerModule,
139143
],
140144
declarations: [KitchenSink, TestEntryComponent],
141145
exports: [KitchenSink, TestEntryComponent],

src/youtube-player/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ ng_module(
2020
),
2121
module_name = "@angular/youtube-player",
2222
deps = [
23-
"@npm//@angular/common",
2423
"@npm//@angular/core",
2524
"@npm//@types/youtube",
2625
"@npm//rxjs",

src/youtube-player/youtube-module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {CommonModule} from '@angular/common';
109
import {NgModule} from '@angular/core';
1110

1211
import {YouTubePlayer} from './youtube-player';
@@ -16,7 +15,6 @@ const COMPONENTS = [YouTubePlayer];
1615
@NgModule({
1716
declarations: COMPONENTS,
1817
exports: COMPONENTS,
19-
imports: [CommonModule],
2018
})
2119
export class YouTubePlayerModule {
2220
}

src/youtube-player/youtube-player.ts

Lines changed: 48 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ interface Player extends YT.Player {
6969
// The only field available is destroy and addEventListener.
7070
type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListener'>;
7171

72+
/**
73+
* Whether we're currently rendering inside a browser. Equivalent of `Platform.isBrowser`,
74+
* but copied over here so we don't have to add another dependency.
75+
*/
76+
const isBrowser = typeof window === 'object' && !!window;
77+
7278
/**
7379
* Angular component that renders a YouTube player via the YouTube player
7480
* iframe API.
@@ -80,7 +86,7 @@ type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListene
8086
changeDetection: ChangeDetectionStrategy.OnPush,
8187
encapsulation: ViewEncapsulation.None,
8288
// This div is *replaced* by the YouTube player embed.
83-
template: '<div #youtube_container></div>',
89+
template: '<div #youtubeContainer></div>',
8490
})
8591
export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
8692
/** YouTube Video ID to view */
@@ -147,15 +153,21 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
147153
@Output() playbackRateChange = new EventEmitter<YT.OnPlaybackRateChangeEvent>();
148154

149155
/** The element that will be replaced by the iframe. */
150-
@ViewChild('youtube_container', {static: false}) youtubeContainer: ElementRef | undefined;
156+
@ViewChild('youtubeContainer', {static: false})
157+
youtubeContainer: ElementRef<HTMLElement>;
158+
151159
private _youtubeContainer = new EventEmitter<HTMLElement>();
152160
private _destroyed = new EventEmitter<undefined>();
153-
154161
private _player: Player | undefined;
155162

156163
constructor(private _ngZone: NgZone) {}
157164

158165
ngOnInit() {
166+
// Don't do anything if we're not in a browser environment.
167+
if (!isBrowser) {
168+
return;
169+
}
170+
159171
let iframeApiAvailableObs: Observable<boolean> = observableOf(true);
160172
if (!window.YT) {
161173
if (this.showBeforeIframeApiLoads) {
@@ -223,19 +235,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
223235
}
224236

225237
ngAfterViewInit() {
226-
if (!this.youtubeContainer) {
227-
return;
228-
}
229238
this._youtubeContainer.emit(this.youtubeContainer.nativeElement);
230239
}
231240

232241
ngOnDestroy() {
233-
if (!this._player) {
234-
return;
242+
if (this._player) {
243+
this._player.destroy();
244+
window.onYouTubeIframeAPIReady = undefined;
245+
this._destroyed.emit();
235246
}
236-
this._player.destroy();
237-
window.onYouTubeIframeAPIReady = undefined;
238-
this._destroyed.emit();
239247
}
240248

241249
private _runInZone<T extends (...args: any[]) => void>(callback: T):
@@ -249,166 +257,122 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
249257

250258
/** See https://developers.google.com/youtube/iframe_api_reference#playVideo */
251259
playVideo() {
252-
if (!this._player) {
253-
return;
260+
if (this._player) {
261+
this._player.playVideo();
254262
}
255-
this._player.playVideo();
256263
}
257264

258265
/** See https://developers.google.com/youtube/iframe_api_reference#pauseVideo */
259266
pauseVideo() {
260-
if (!this._player) {
261-
return;
267+
if (this._player) {
268+
this._player.pauseVideo();
262269
}
263-
this._player.pauseVideo();
264270
}
265271

266272
/** See https://developers.google.com/youtube/iframe_api_reference#stopVideo */
267273
stopVideo() {
268-
if (!this._player) {
269-
return;
274+
if (this._player) {
275+
this._player.stopVideo();
270276
}
271-
this._player.stopVideo();
272277
}
273278

274279
/** See https://developers.google.com/youtube/iframe_api_reference#seekTo */
275280
seekTo(seconds: number, allowSeekAhead: boolean) {
276-
if (!this._player) {
277-
return;
281+
if (this._player) {
282+
this._player.seekTo(seconds, allowSeekAhead);
278283
}
279-
this._player.seekTo(seconds, allowSeekAhead);
280284
}
281285

282286
/** See https://developers.google.com/youtube/iframe_api_reference#mute */
283287
mute() {
284-
if (!this._player) {
285-
return;
288+
if (this._player) {
289+
this._player.mute();
286290
}
287-
this._player.mute();
288291
}
289292

290293
/** See https://developers.google.com/youtube/iframe_api_reference#unMute */
291294
unMute() {
292-
if (!this._player) {
293-
return;
295+
if (this._player) {
296+
this._player.unMute();
294297
}
295-
this._player.unMute();
296298
}
297299

298300
/** See https://developers.google.com/youtube/iframe_api_reference#isMuted */
299301
isMuted(): boolean {
300-
if (!this._player) {
301-
return false;
302-
}
303-
return this._player.isMuted();
302+
return !this._player || this._player.isMuted();
304303
}
305304

306305
/** See https://developers.google.com/youtube/iframe_api_reference#setVolume */
307306
setVolume(volume: number) {
308-
if (!this._player) {
309-
return;
307+
if (this._player) {
308+
this._player.setVolume(volume);
310309
}
311-
this._player.setVolume(volume);
312310
}
313311

314312
/** See https://developers.google.com/youtube/iframe_api_reference#getVolume */
315313
getVolume(): number {
316-
if (!this._player) {
317-
return 0;
318-
}
319-
return this._player.getVolume();
314+
return this._player ? this._player.getVolume() : 0;
320315
}
321316

322317
/** See https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate */
323318
setPlaybackRate(playbackRate: number) {
324-
if (!this._player) {
325-
return;
319+
if (this._player) {
320+
return this._player.setPlaybackRate(playbackRate);
326321
}
327-
return this._player.setPlaybackRate(playbackRate);
328322
}
329323

330324
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate */
331325
getPlaybackRate(): number {
332-
if (!this._player) {
333-
return 0;
334-
}
335-
return this._player.getPlaybackRate();
326+
return this._player ? this._player.getPlaybackRate() : 0;
336327
}
337328

338329
/** See https://developers.google.com/youtube/iframe_api_reference#getAvailablePlaybackRates */
339330
getAvailablePlaybackRates(): number[] {
340-
if (!this._player) {
341-
return [];
342-
}
343-
return this._player.getAvailablePlaybackRates();
331+
return this._player ? this._player.getAvailablePlaybackRates() : [];
344332
}
345333

346334
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction */
347335
getVideoLoadedFraction(): number {
348-
if (!this._player) {
349-
return 0;
350-
}
351-
return this._player.getVideoLoadedFraction();
336+
return this._player ? this._player.getVideoLoadedFraction() : 0;
352337
}
353338

354339
/** See https://developers.google.com/youtube/iframe_api_reference#getPlayerState */
355340
getPlayerState(): YT.PlayerState | undefined {
356-
if (!window.YT) {
341+
if (!isBrowser || !window.YT) {
357342
return undefined;
358343
}
359344

360-
if (!this._player) {
361-
return YT.PlayerState.UNSTARTED;
362-
}
363-
return this._player.getPlayerState();
345+
return this._player ? this._player.getPlayerState() : YT.PlayerState.UNSTARTED;
364346
}
365347

366348
/** See https://developers.google.com/youtube/iframe_api_reference#getCurrentTime */
367349
getCurrentTime(): number {
368-
if (!this._player) {
369-
return 0;
370-
}
371-
return this._player.getCurrentTime();
350+
return this._player ? this._player.getCurrentTime() : 0;
372351
}
373352

374353
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality */
375354
getPlaybackQuality(): YT.SuggestedVideoQuality {
376-
if (!this._player) {
377-
return 'default';
378-
}
379-
return this._player.getPlaybackQuality();
355+
return this._player ? this._player.getPlaybackQuality() : 'default';
380356
}
381357

382358
/** See https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels */
383359
getAvailableQualityLevels(): YT.SuggestedVideoQuality[] {
384-
if (!this._player) {
385-
return [];
386-
}
387-
return this._player.getAvailableQualityLevels();
360+
return this._player ? this._player.getAvailableQualityLevels() : [];
388361
}
389362

390363
/** See https://developers.google.com/youtube/iframe_api_reference#getDuration */
391364
getDuration(): number {
392-
if (!this._player) {
393-
return 0;
394-
}
395-
return this._player.getDuration();
365+
return this._player ? this._player.getDuration() : 0;
396366
}
397367

398368
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoUrl */
399369
getVideoUrl(): string {
400-
if (!this._player) {
401-
return '';
402-
}
403-
return this._player.getVideoUrl();
370+
return this._player ? this._player.getVideoUrl() : '';
404371
}
405372

406373
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode */
407374
getVideoEmbedCode(): string {
408-
if (!this._player) {
409-
return '';
410-
}
411-
return this._player.getVideoEmbedCode();
375+
return this._player ? this._player.getVideoEmbedCode() : '';
412376
}
413377
}
414378

0 commit comments

Comments
 (0)