gristlabs_grist-core/app/client/ui/YouTubePlayer.ts
George Gevoian 4740f1f933 (core) Update onboarding flow
Summary:
A new onboarding page is now shown to all new users visiting the doc
menu for the first time. Tutorial cards on the doc menu have been
replaced with a new version that tracks completion progress, alongside
a new card that opens the orientation video.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4296
2024-07-23 11:49:23 -04:00

138 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;
}
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,
});
}
}