Skip to content

Commit

Permalink
fix(youtube-player): errors during server-side rendering
Browse files Browse the repository at this point in the history
Fixes the `youtube-player` component throwing errors if it's rendered on the server. Also does some minor code cleanup.
  • Loading branch information
crisbeto committed Sep 16, 2019
1 parent 4778f49 commit a72672a
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 87 deletions.
1 change: 1 addition & 0 deletions src/universal-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ng_module(
],
deps = [
"@npm//@angular/platform-server",
"//src/youtube-player",
] + CDK_TARGETS + CDK_EXPERIMENTAL_TARGETS + MATERIAL_TARGETS + MATERIAL_EXPERIMENTAL_TARGETS,
)

Expand Down
4 changes: 4 additions & 0 deletions src/universal-app/kitchen-sink/kitchen-sink.html
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,7 @@ <h2>Virtual scroll</h2>
Item #{{i}}
</div>
</cdk-virtual-scroll-viewport>

<h2>YouTube player</h2>

<youtube-player videoId="dQw4w9WgXcQ"></youtube-player>
4 changes: 4 additions & 0 deletions src/universal-app/kitchen-sink/kitchen-sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatSortModule} from '@angular/material/sort';
import {MatStepperModule} from '@angular/material/stepper';
import {YouTubePlayerModule} from '@angular/youtube-player';
import {Observable, of as observableOf} from 'rxjs';

export class TableDataSource extends DataSource<any> {
Expand Down Expand Up @@ -136,6 +137,9 @@ export class KitchenSink {
// CDK Modules
CdkTableModule,
DragDropModule,

// Other modules
YouTubePlayerModule,
],
declarations: [KitchenSink, TestEntryComponent],
exports: [KitchenSink, TestEntryComponent],
Expand Down
1 change: 0 additions & 1 deletion src/youtube-player/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ ng_module(
),
module_name = "@angular/youtube-player",
deps = [
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//@types/youtube",
"@npm//rxjs",
Expand Down
2 changes: 0 additions & 2 deletions src/youtube-player/youtube-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';

import {YouTubePlayer} from './youtube-player';
Expand All @@ -16,7 +15,6 @@ const COMPONENTS = [YouTubePlayer];
@NgModule({
declarations: COMPONENTS,
exports: COMPONENTS,
imports: [CommonModule],
})
export class YouTubePlayerModule {
}
132 changes: 48 additions & 84 deletions src/youtube-player/youtube-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ interface Player extends YT.Player {
// The only field available is destroy and addEventListener.
type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListener'>;

/**
* Whether we're currently rendering inside a browser. Equivalent of `Platform.isBrowser`,
* but copied over here so we don't have to add another dependency.
*/
const isBrowser = typeof window === 'object' && !!window;

/**
* Angular component that renders a YouTube player via the YouTube player
* iframe API.
Expand All @@ -80,7 +86,7 @@ type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListene
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
// This div is *replaced* by the YouTube player embed.
template: '<div #youtube_container></div>',
template: '<div #youtubeContainer></div>',
})
export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
/** YouTube Video ID to view */
Expand Down Expand Up @@ -147,15 +153,21 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
@Output() playbackRateChange = new EventEmitter<YT.OnPlaybackRateChangeEvent>();

/** The element that will be replaced by the iframe. */
@ViewChild('youtube_container', {static: false}) youtubeContainer: ElementRef | undefined;
@ViewChild('youtubeContainer', {static: false})
youtubeContainer: ElementRef<HTMLElement>;

private _youtubeContainer = new EventEmitter<HTMLElement>();
private _destroyed = new EventEmitter<undefined>();

private _player: Player | undefined;

constructor(private _ngZone: NgZone) {}

ngOnInit() {
// Don't do anything if we're not in a browser environment.
if (!isBrowser) {
return;
}

let iframeApiAvailableObs: Observable<boolean> = observableOf(true);
if (!window.YT) {
if (this.showBeforeIframeApiLoads) {
Expand Down Expand Up @@ -223,19 +235,15 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy, OnInit {
}

ngAfterViewInit() {
if (!this.youtubeContainer) {
return;
}
this._youtubeContainer.emit(this.youtubeContainer.nativeElement);
}

ngOnDestroy() {
if (!this._player) {
return;
if (this._player) {
this._player.destroy();
window.onYouTubeIframeAPIReady = undefined;
this._destroyed.emit();
}
this._player.destroy();
window.onYouTubeIframeAPIReady = undefined;
this._destroyed.emit();
}

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

/** See https://developers.google.com/youtube/iframe_api_reference#playVideo */
playVideo() {
if (!this._player) {
return;
if (this._player) {
this._player.playVideo();
}
this._player.playVideo();
}

/** See https://developers.google.com/youtube/iframe_api_reference#pauseVideo */
pauseVideo() {
if (!this._player) {
return;
if (this._player) {
this._player.pauseVideo();
}
this._player.pauseVideo();
}

/** See https://developers.google.com/youtube/iframe_api_reference#stopVideo */
stopVideo() {
if (!this._player) {
return;
if (this._player) {
this._player.stopVideo();
}
this._player.stopVideo();
}

/** See https://developers.google.com/youtube/iframe_api_reference#seekTo */
seekTo(seconds: number, allowSeekAhead: boolean) {
if (!this._player) {
return;
if (this._player) {
this._player.seekTo(seconds, allowSeekAhead);
}
this._player.seekTo(seconds, allowSeekAhead);
}

/** See https://developers.google.com/youtube/iframe_api_reference#mute */
mute() {
if (!this._player) {
return;
if (this._player) {
this._player.mute();
}
this._player.mute();
}

/** See https://developers.google.com/youtube/iframe_api_reference#unMute */
unMute() {
if (!this._player) {
return;
if (this._player) {
this._player.unMute();
}
this._player.unMute();
}

/** See https://developers.google.com/youtube/iframe_api_reference#isMuted */
isMuted(): boolean {
if (!this._player) {
return false;
}
return this._player.isMuted();
return !this._player || this._player.isMuted();
}

/** See https://developers.google.com/youtube/iframe_api_reference#setVolume */
setVolume(volume: number) {
if (!this._player) {
return;
if (this._player) {
this._player.setVolume(volume);
}
this._player.setVolume(volume);
}

/** See https://developers.google.com/youtube/iframe_api_reference#getVolume */
getVolume(): number {
if (!this._player) {
return 0;
}
return this._player.getVolume();
return this._player ? this._player.getVolume() : 0;
}

/** See https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate */
setPlaybackRate(playbackRate: number) {
if (!this._player) {
return;
if (this._player) {
return this._player.setPlaybackRate(playbackRate);
}
return this._player.setPlaybackRate(playbackRate);
}

/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate */
getPlaybackRate(): number {
if (!this._player) {
return 0;
}
return this._player.getPlaybackRate();
return this._player ? this._player.getPlaybackRate() : 0;
}

/** See https://developers.google.com/youtube/iframe_api_reference#getAvailablePlaybackRates */
getAvailablePlaybackRates(): number[] {
if (!this._player) {
return [];
}
return this._player.getAvailablePlaybackRates();
return this._player ? this._player.getAvailablePlaybackRates() : [];
}

/** See https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction */
getVideoLoadedFraction(): number {
if (!this._player) {
return 0;
}
return this._player.getVideoLoadedFraction();
return this._player ? this._player.getVideoLoadedFraction() : 0;
}

/** See https://developers.google.com/youtube/iframe_api_reference#getPlayerState */
getPlayerState(): YT.PlayerState | undefined {
if (!window.YT) {
if (!isBrowser || !window.YT) {
return undefined;
}

if (!this._player) {
return YT.PlayerState.UNSTARTED;
}
return this._player.getPlayerState();
return this._player ? this._player.getPlayerState() : YT.PlayerState.UNSTARTED;
}

/** See https://developers.google.com/youtube/iframe_api_reference#getCurrentTime */
getCurrentTime(): number {
if (!this._player) {
return 0;
}
return this._player.getCurrentTime();
return this._player ? this._player.getCurrentTime() : 0;
}

/** See https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality */
getPlaybackQuality(): YT.SuggestedVideoQuality {
if (!this._player) {
return 'default';
}
return this._player.getPlaybackQuality();
return this._player ? this._player.getPlaybackQuality() : 'default';
}

/** See https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels */
getAvailableQualityLevels(): YT.SuggestedVideoQuality[] {
if (!this._player) {
return [];
}
return this._player.getAvailableQualityLevels();
return this._player ? this._player.getAvailableQualityLevels() : [];
}

/** See https://developers.google.com/youtube/iframe_api_reference#getDuration */
getDuration(): number {
if (!this._player) {
return 0;
}
return this._player.getDuration();
return this._player ? this._player.getDuration() : 0;
}

/** See https://developers.google.com/youtube/iframe_api_reference#getVideoUrl */
getVideoUrl(): string {
if (!this._player) {
return '';
}
return this._player.getVideoUrl();
return this._player ? this._player.getVideoUrl() : '';
}

/** See https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode */
getVideoEmbedCode(): string {
if (!this._player) {
return '';
}
return this._player.getVideoEmbedCode();
return this._player ? this._player.getVideoEmbedCode() : '';
}
}

Expand Down

0 comments on commit a72672a

Please sign in to comment.