Skip to content

Commit e35ff44

Browse files
committed
fix(youtube-player): errors during server-side rendering
Fixes the `youtube-player` component throwing errors if it's rendered on the server. Also does some minor code cleanup.
1 parent 4778f49 commit e35ff44

File tree

6 files changed

+54
-87
lines changed

6 files changed

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

72+
/** Whether we're currently rendering inside a browser. */
73+
const isBrowser = typeof window === 'object' && !!window;
74+
7275
/**
7376
* Angular component that renders a YouTube player via the YouTube player
7477
* iframe API.
@@ -80,7 +83,7 @@ type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListene
8083
changeDetection: ChangeDetectionStrategy.OnPush,
8184
encapsulation: ViewEncapsulation.None,
8285
// This div is *replaced* by the YouTube player embed.
83-
template: '<div #youtube_container></div>',
86+
template: '<div #youtubeContainer></div>',
8487
})
8588
export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
8689
/** YouTube Video ID to view */
@@ -147,15 +150,21 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
147150
@Output() playbackRateChange = new EventEmitter<YT.OnPlaybackRateChangeEvent>();
148151

149152
/** The element that will be replaced by the iframe. */
150-
@ViewChild('youtube_container', {static: false}) youtubeContainer: ElementRef | undefined;
153+
@ViewChild('youtubeContainer', {static: false})
154+
youtubeContainer: ElementRef<HTMLElement>;
155+
151156
private _youtubeContainer = new EventEmitter<HTMLElement>();
152157
private _destroyed = new EventEmitter<undefined>();
153-
154158
private _player: Player | undefined;
155159

156160
constructor(private _ngZone: NgZone) {}
157161

158162
ngOnInit() {
163+
// Don't do anything if we're not in a browser environment.
164+
if (!isBrowser) {
165+
return;
166+
}
167+
159168
let iframeApiAvailableObs: Observable<boolean> = observableOf(true);
160169
if (!window.YT) {
161170
if (this.showBeforeIframeApiLoads) {
@@ -223,19 +232,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
223232
}
224233

225234
ngAfterViewInit() {
226-
if (!this.youtubeContainer) {
227-
return;
228-
}
229235
this._youtubeContainer.emit(this.youtubeContainer.nativeElement);
230236
}
231237

232238
ngOnDestroy() {
233-
if (!this._player) {
234-
return;
239+
if (this._player) {
240+
this._player.destroy();
241+
window.onYouTubeIframeAPIReady = undefined;
242+
this._destroyed.emit();
235243
}
236-
this._player.destroy();
237-
window.onYouTubeIframeAPIReady = undefined;
238-
this._destroyed.emit();
239244
}
240245

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

250255
/** See https://developers.google.com/youtube/iframe_api_reference#playVideo */
251256
playVideo() {
252-
if (!this._player) {
253-
return;
257+
if (this._player) {
258+
this._player.playVideo();
254259
}
255-
this._player.playVideo();
256260
}
257261

258262
/** See https://developers.google.com/youtube/iframe_api_reference#pauseVideo */
259263
pauseVideo() {
260-
if (!this._player) {
261-
return;
264+
if (this._player) {
265+
this._player.pauseVideo();
262266
}
263-
this._player.pauseVideo();
264267
}
265268

266269
/** See https://developers.google.com/youtube/iframe_api_reference#stopVideo */
267270
stopVideo() {
268-
if (!this._player) {
269-
return;
271+
if (this._player) {
272+
this._player.stopVideo();
270273
}
271-
this._player.stopVideo();
272274
}
273275

274276
/** See https://developers.google.com/youtube/iframe_api_reference#seekTo */
275277
seekTo(seconds: number, allowSeekAhead: boolean) {
276-
if (!this._player) {
277-
return;
278+
if (this._player) {
279+
this._player.seekTo(seconds, allowSeekAhead);
278280
}
279-
this._player.seekTo(seconds, allowSeekAhead);
280281
}
281282

282283
/** See https://developers.google.com/youtube/iframe_api_reference#mute */
283284
mute() {
284-
if (!this._player) {
285-
return;
285+
if (this._player) {
286+
this._player.mute();
286287
}
287-
this._player.mute();
288288
}
289289

290290
/** See https://developers.google.com/youtube/iframe_api_reference#unMute */
291291
unMute() {
292-
if (!this._player) {
293-
return;
292+
if (this._player) {
293+
this._player.unMute();
294294
}
295-
this._player.unMute();
296295
}
297296

298297
/** See https://developers.google.com/youtube/iframe_api_reference#isMuted */
299298
isMuted(): boolean {
300-
if (!this._player) {
301-
return false;
302-
}
303-
return this._player.isMuted();
299+
return !this._player || this._player.isMuted();
304300
}
305301

306302
/** See https://developers.google.com/youtube/iframe_api_reference#setVolume */
307303
setVolume(volume: number) {
308-
if (!this._player) {
309-
return;
304+
if (this._player) {
305+
this._player.setVolume(volume);
310306
}
311-
this._player.setVolume(volume);
312307
}
313308

314309
/** See https://developers.google.com/youtube/iframe_api_reference#getVolume */
315310
getVolume(): number {
316-
if (!this._player) {
317-
return 0;
318-
}
319-
return this._player.getVolume();
311+
return this._player ? this._player.getVolume() : 0;
320312
}
321313

322314
/** See https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate */
323315
setPlaybackRate(playbackRate: number) {
324-
if (!this._player) {
325-
return;
316+
if (this._player) {
317+
return this._player.setPlaybackRate(playbackRate);
326318
}
327-
return this._player.setPlaybackRate(playbackRate);
328319
}
329320

330321
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate */
331322
getPlaybackRate(): number {
332-
if (!this._player) {
333-
return 0;
334-
}
335-
return this._player.getPlaybackRate();
323+
return this._player ? this._player.getPlaybackRate() : 0;
336324
}
337325

338326
/** See https://developers.google.com/youtube/iframe_api_reference#getAvailablePlaybackRates */
339327
getAvailablePlaybackRates(): number[] {
340-
if (!this._player) {
341-
return [];
342-
}
343-
return this._player.getAvailablePlaybackRates();
328+
return this._player ? this._player.getAvailablePlaybackRates() : [];
344329
}
345330

346331
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction */
347332
getVideoLoadedFraction(): number {
348-
if (!this._player) {
349-
return 0;
350-
}
351-
return this._player.getVideoLoadedFraction();
333+
return this._player ? this._player.getVideoLoadedFraction() : 0;
352334
}
353335

354336
/** See https://developers.google.com/youtube/iframe_api_reference#getPlayerState */
355337
getPlayerState(): YT.PlayerState | undefined {
356-
if (!window.YT) {
338+
if (isBrowser && !window.YT) {
357339
return undefined;
358340
}
359341

360-
if (!this._player) {
361-
return YT.PlayerState.UNSTARTED;
362-
}
363-
return this._player.getPlayerState();
342+
return this._player ? this._player.getPlayerState() : YT.PlayerState.UNSTARTED;
364343
}
365344

366345
/** See https://developers.google.com/youtube/iframe_api_reference#getCurrentTime */
367346
getCurrentTime(): number {
368-
if (!this._player) {
369-
return 0;
370-
}
371-
return this._player.getCurrentTime();
347+
return this._player ? this._player.getCurrentTime() : 0;
372348
}
373349

374350
/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality */
375351
getPlaybackQuality(): YT.SuggestedVideoQuality {
376-
if (!this._player) {
377-
return 'default';
378-
}
379-
return this._player.getPlaybackQuality();
352+
return this._player ? this._player.getPlaybackQuality() : 'default';
380353
}
381354

382355
/** See https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels */
383356
getAvailableQualityLevels(): YT.SuggestedVideoQuality[] {
384-
if (!this._player) {
385-
return [];
386-
}
387-
return this._player.getAvailableQualityLevels();
357+
return this._player ? this._player.getAvailableQualityLevels() : [];
388358
}
389359

390360
/** See https://developers.google.com/youtube/iframe_api_reference#getDuration */
391361
getDuration(): number {
392-
if (!this._player) {
393-
return 0;
394-
}
395-
return this._player.getDuration();
362+
return this._player ? this._player.getDuration() : 0;
396363
}
397364

398365
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoUrl */
399366
getVideoUrl(): string {
400-
if (!this._player) {
401-
return '';
402-
}
403-
return this._player.getVideoUrl();
367+
return this._player ? this._player.getVideoUrl() : '';
404368
}
405369

406370
/** See https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode */
407371
getVideoEmbedCode(): string {
408-
if (!this._player) {
409-
return '';
410-
}
411-
return this._player.getVideoEmbedCode();
372+
return this._player ? this._player.getVideoEmbedCode() : '';
412373
}
413374
}
414375

0 commit comments

Comments
 (0)