UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

267 lines (220 loc) 8.15 kB
import { Texture, Scene, Tools, Nullable } from "@babylonjs/core"; /* Based on babylonjs VideoTexture with tweaks */ /** * Settings for finer control over video usage */ export interface VideoTextureSettings { autoPlay?: boolean; // Applies `autoplay` to video, if specified muted?: boolean; // Applies `muted` to video, if specified loop?: boolean; // Applies `loop` to video, if specified autoUpdateTexture: boolean; // Automatically updates internal texture from video at every frame in the render loop onVideoError?: () => void; // if there was an error autoplaying, run this callback (used to be observable) } /** * If you want to display a video in your scene, this is the special texture for that. * This special texture works similar to other textures, with the exception of a few parameters. */ export class CustomVideoTexture extends Texture { /** * Tells whether textures will be updated automatically or user is required to call `updateTexture` manually */ public readonly autoUpdateTexture: boolean; /** * The video instance used by the texture internally */ // public readonly video: HTMLVideoElement; public video: HTMLVideoElement; private _generateMipMaps: boolean; private _stillImageCaptured = false; private _settings: VideoTextureSettings; private _frameId = -1; private _currentSrc: Nullable<HTMLVideoElement> = null; /** * Creates a video texture. * If you want to display a video in your scene, this is the special texture for that. * This special texture works similar to other textures, with the exception of a few parameters. * @see https://doc.babylonjs.com/how_to/video_texture * @param name optional name, will detect from video source, if not defined * @param src can be used to provide an url, array of urls or an already setup HTML video element. * @param scene is obviously the current scene. * @param generateMipMaps can be used to turn on mipmaps (Can be expensive for videoTextures because they are often updated). * @param invertY is false by default but can be used to invert video on Y axis * @param samplingMode controls the sampling method and is set to TRILINEAR_SAMPLINGMODE by default * @param settings allows finer control over video usage */ constructor( name: Nullable<string>, src: HTMLVideoElement, scene: Nullable<Scene>, generateMipMaps = false, invertY = false, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, settings: VideoTextureSettings = { autoPlay: false, loop: true, autoUpdateTexture: true, } ) { super(null, scene, !generateMipMaps, invertY); this._generateMipMaps = generateMipMaps; this._initialSamplingMode = samplingMode; this.autoUpdateTexture = settings.autoUpdateTexture; this._currentSrc = src; this.name = name || this._getName(src); this.video = src; this._settings = settings; if (settings.autoPlay !== undefined) { this.video.autoplay = settings.autoPlay; } if (settings.loop !== undefined) { this.video.loop = settings.loop; } if (settings.muted !== undefined) { this.video.muted = settings.muted; } this.video.setAttribute("playsinline", ""); this.video.addEventListener("playing", this._updateInternalTexture); // custom this.video.addEventListener("paused", this._updateInternalTexture); this.video.addEventListener("seeked", this._updateInternalTexture); this.video.addEventListener("emptied", this.reset); this.video.addEventListener("canplay", this._createInternalTexture); if (settings.autoPlay) { this.video.play(); } const videoHasEnoughData = this.video.readyState >= this.video.HAVE_CURRENT_DATA; if (videoHasEnoughData) { this._createInternalTexture(); } } private _getName(src: string | string[] | HTMLVideoElement): string { if (src instanceof HTMLVideoElement) { return src.currentSrc; } if (typeof src === "object") { return src.toString(); } return src; } private _createInternalTexture = (): void => { if (this._texture != null) { return; } if ( !this._getEngine()!.needPOTTextures || (Tools.IsExponentOfTwo(this.video.videoWidth) && Tools.IsExponentOfTwo(this.video.videoHeight)) ) { this.wrapU = Texture.WRAP_ADDRESSMODE; this.wrapV = Texture.WRAP_ADDRESSMODE; } else { this.wrapU = Texture.CLAMP_ADDRESSMODE; this.wrapV = Texture.CLAMP_ADDRESSMODE; this._generateMipMaps = false; } this._texture = this._getEngine()!.createDynamicTexture( this.video.videoWidth, this.video.videoHeight, this._generateMipMaps, // false, this.samplingMode ); if (!this.video.autoplay) { } else { this._updateInternalTexture(); if (this.onLoadObservable.hasObservers()) { this.onLoadObservable.notifyObservers(this); } } }; public reset = (): void => { if (this._texture == null) { return; } this._texture.dispose(); this._texture = null; }; /** * @hidden Internal method to initiate `update`. */ public _rebuild(): void { this.update(); } /** * Update Texture in the `auto` mode. Does not do anything if `settings.autoUpdateTexture` is false. */ public update(): void { if (!this.autoUpdateTexture) { // Expecting user to call `updateTexture` manually return; } this.updateTexture(true); } /** * Update Texture in `manual` mode. Does not do anything if not visible or paused. * @param isVisible Visibility state, detected by user using `scene.getActiveMeshes()` or otherwise. */ public updateTexture(isVisible: boolean): void { if (!isVisible) { return; } if (this.video.paused && this._stillImageCaptured) { return; } this._stillImageCaptured = true; this._updateInternalTexture(); } protected _updateInternalTexture = (): void => { if (this._texture == null) { return; } if (this.video.readyState < this.video.HAVE_CURRENT_DATA) { return; } let frameId = this.getScene()!.getFrameId(); if (this._frameId === frameId) { return; } this._frameId = frameId; this._getEngine()!.updateVideoTexture(this._texture, this.video, this._invertY); }; public updateVid(newVid: HTMLVideoElement): void { // if (this.video === newVid) { // return; // } // dispose some video stuff this._currentSrc = null; this.video.removeEventListener("playing", this._updateInternalTexture); // custom this.video.removeEventListener("canplay", this._createInternalTexture); this.video.removeEventListener("paused", this._updateInternalTexture); this.video.removeEventListener("seeked", this._updateInternalTexture); this.video.removeEventListener("emptied", this.reset); // this.video = newVid; this._currentSrc = newVid; // readd some stuff this.video.setAttribute("playsinline", ""); this.video.addEventListener("playing", this._updateInternalTexture); // custom this.video.addEventListener("paused", this._updateInternalTexture); this.video.addEventListener("seeked", this._updateInternalTexture); this.video.addEventListener("emptied", this.reset); this.video.addEventListener("canplay", this._createInternalTexture); this._updateInternalTexture(); if (this.onLoadObservable.hasObservers()) { this.onLoadObservable.notifyObservers(this); } } /** * Dispose the texture and release its associated resources. */ public dispose(): void { super.dispose(); this._currentSrc = null; this.video.removeEventListener("playing", this._updateInternalTexture); // custom this.video.removeEventListener("canplay", this._createInternalTexture); this.video.removeEventListener("paused", this._updateInternalTexture); this.video.removeEventListener("seeked", this._updateInternalTexture); this.video.removeEventListener("emptied", this.reset); // this.video.pause(); } }