gristlabs_grist-core/app/client/ui/YouTubePlayer.ts
Dmitry S 69aabd1ae0 (core) Limit related videos when playing onboarding video tour from home page
Summary:
When video is opened from the app homepage, it opens in a popup, which stays
open when it ends. The rel=0 parameter limits the related videos shown at the
end to those from the same channel, avoiding surprising unrelated videos.

This doesn't affect the video shown during initial onboarding, since that once
auto-closes when it ends.

Test Plan: Tested manually

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D4313
2024-08-09 19:24:09 -04:00

139 lines
3.3 KiB
TypeScript

import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
import {waitObs} from 'app/common/gutil';
import {Disposable, dom, DomElementArg} from 'grainjs';
import ko from 'knockout';
export interface Player {
playVideo(): void;
pauseVideo(): void;
stopVideo(): void;
mute(): void;
unMute(): void;
setVolume(volume: number): void;
getCurrentTime(): number;
getPlayerState(): PlayerState;
}
export interface PlayerOptions {
height?: string;
width?: string;
origin?: string;
playerVars?: PlayerVars;
onPlayerReady?(player: Player): void
onPlayerStateChange?(player: Player, event: PlayerStateChangeEvent): void;
}
export interface PlayerVars {
controls?: 0 | 1;
disablekb?: 0 | 1;
fs?: 0 | 1;
iv_load_policy?: 1 | 3;
modestbranding?: 0 | 1;
rel?: 0 | 1;
}
export interface PlayerStateChangeEvent {
data: PlayerState;
}
export enum PlayerState {
Unstarted = -1,
Ended = 0,
Playing = 1,
Paused = 2,
Buffering = 3,
VideoCued = 5,
}
const G = getBrowserGlobals('document', 'window');
/**
* Wrapper component for the YouTube IFrame Player API.
*
* Fetches the JavaScript code for the API if needed, and creates an iframe that
* points to a YouTube video with the specified id.
*
* For more documentation, see https://developers.google.com/youtube/iframe_api_reference.
*/
export class YouTubePlayer extends Disposable {
private _domArgs: DomElementArg[];
private _isLoading: ko.Observable<boolean> = ko.observable(true);
private _playerId = `youtube-player-${this._videoId}`;
private _player: Player;
constructor(
private _videoId: string,
private _options: PlayerOptions,
...domArgs: DomElementArg[]
) {
super();
this._domArgs = domArgs;
if (!G.window.YT) {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag?.parentNode?.insertBefore(tag, firstScriptTag);
G.window.onYouTubeIframeAPIReady = () => this._handleYouTubeIframeAPIReady();
} else {
setTimeout(() => this._handleYouTubeIframeAPIReady(), 0);
}
}
public isLoading() {
return this._isLoading();
}
public isLoaded() {
return waitObs(this._isLoading, (val) => !val);
}
public play() {
this._player.playVideo();
}
public pause() {
this._player.pauseVideo();
}
public playPause() {
if (this._player.getPlayerState() === PlayerState.Playing) {
this._player.pauseVideo();
} else {
this._player.playVideo();
}
}
public setVolume(volume: number) {
this._player.setVolume(volume);
}
public getCurrentTime(): number {
return this._player.getCurrentTime();
}
public buildDom() {
return dom('div', {id: this._playerId}, ...this._domArgs);
}
private _handleYouTubeIframeAPIReady() {
const {onPlayerReady, onPlayerStateChange, playerVars, ...otherOptions} = this._options;
this._player = new G.window.YT.Player(this._playerId, {
videoId: this._videoId,
playerVars,
events: {
onReady: () => {
this._isLoading(false);
onPlayerReady?.(this._player);
},
onStateChange: (event: PlayerStateChangeEvent) =>
onPlayerStateChange?.(this._player, event),
},
...otherOptions,
});
}
}