소스 검색

More flexible Video Texture

Allows to override `autoPlay` and `loop` values
Allows to use manual texture updating
Maintains backwards-compatibility
Edgar Simson 7 년 전
부모
커밋
8e50895b9f
1개의 변경된 파일132개의 추가작업 그리고 60개의 파일을 삭제
  1. 132 60
      src/Materials/Textures/babylon.videoTexture.ts

+ 132 - 60
src/Materials/Textures/babylon.videoTexture.ts

@@ -1,44 +1,106 @@
 module BABYLON {
+    export interface VideoTextureSettings {
+        autoPlay?: boolean;
+        loop?: boolean;
+        autoUpdateTexture: boolean;
+    }
+
+    const getName = (src: string | string[] | HTMLVideoElement): string => {
+        if (src instanceof HTMLVideoElement) {
+            return src.currentSrc;
+        }
+
+        if (typeof src === "object") {
+            return src.toString();
+        }
+
+        return src;
+    };
+
+    const getVideo = (src: string | string[] | HTMLVideoElement): HTMLVideoElement => {
+        if (src instanceof HTMLVideoElement) {
+            return src;
+        }
+        const video: HTMLVideoElement = document.createElement("video");
+        if (typeof src === "string") {
+            video.src = src;
+        } else {
+            src.forEach(url => {
+                const source = document.createElement("source");
+                source.src = url;
+                video.appendChild(source);
+            });
+        }
+        return video;
+    };
+
     export class VideoTexture extends Texture {
-        public video: HTMLVideoElement;
+        readonly autoUpdateTexture: boolean;
+        readonly video: HTMLVideoElement;
 
-        private _autoLaunch = true;
-        private _lastUpdate: number;
-        private _generateMipMaps: boolean
-        private _setTextureReady: () => void;
+        private _generateMipMaps: boolean;
         private _engine: Engine;
 
         /**
          * Creates a video texture.
-         * Sample : https://doc.babylonjs.com/tutorials/01._Advanced_Texturing
-         * @param {Array} urlsOrVideo can be used to provide an array of urls or an already setup HTML video element.
+         * Sample : https://doc.babylonjs.com/how_to/video_texture
+         * @param {string | null} name optional name, will detect from video source, if not defined
+         * @param {(string | string[] | HTMLVideoElement)} src can be used to provide an url, array of urls or an already setup HTML video element.
          * @param {BABYLON.Scene} scene is obviously the current scene.
          * @param {boolean} generateMipMaps can be used to turn on mipmaps (Can be expensive for videoTextures because they are often updated).
          * @param {boolean} invertY is false by default but can be used to invert video on Y axis
          * @param {number} samplingMode controls the sampling method and is set to TRILINEAR_SAMPLINGMODE by default
+         * @param {VideoTextureSettings} [settings] allows finer control over video usage
          */
-        constructor(name: string, urlsOrVideo: string[] | HTMLVideoElement, scene: Scene, generateMipMaps = false, invertY = false, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+        constructor(
+            name: Nullable<string>,
+            src: string | string[] | HTMLVideoElement,
+            scene: Nullable<Scene>,
+            generateMipMaps = false,
+            invertY = false,
+            samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE,
+            settings: VideoTextureSettings = {
+                autoPlay: true,
+                loop: true,
+                autoUpdateTexture: true,
+            }
+        ) {
             super(null, scene, !generateMipMaps, invertY);
 
-            var urls: Nullable<string[]> = null;
-            this.name = name;
+            this._engine = (<Scene>this.getScene()).getEngine();
+            this._generateMipMaps = generateMipMaps;
+            this._samplingMode = samplingMode;
+            this.autoUpdateTexture = settings.autoUpdateTexture;
 
-            if (urlsOrVideo instanceof HTMLVideoElement) {
-                this.video = <any>urlsOrVideo;
-            } else {
-                urls = urlsOrVideo;
+            this.name = name || getName(src);
+            this.video = getVideo(src);
 
-                this.video = document.createElement("video");
-                this.video.autoplay = false;
-                this.video.loop = true;
-                Tools.SetCorsBehavior(urls, this.video);
+            if (settings.autoPlay !== undefined) {
+                this.video.autoplay = settings.autoPlay;
+            }
+            if (settings.loop !== undefined) {
+                this.video.loop = settings.loop;
             }
 
-            this._engine = (<Scene>this.getScene()).getEngine();
-            this._generateMipMaps = generateMipMaps;
-            this._samplingMode = samplingMode;
+            this.video.addEventListener("canplay", this.createInternalTexture);
+            this.video.addEventListener("paused", this.updateInternalTexture);
+            this.video.addEventListener("seeked", this.updateInternalTexture);
+            this.video.addEventListener("emptied", this.reset);
+
+            if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
+                this.createInternalTexture();
+            }
+        }
 
-            if (!this._engine.needPOTTextures || (Tools.IsExponentOfTwo(this.video.videoWidth) && Tools.IsExponentOfTwo(this.video.videoHeight))) {
+        private createInternalTexture = (): void => {
+            if (this._texture != null) {
+                return;
+            }
+
+            if (
+                !this._engine.needPOTTextures ||
+                (Tools.IsExponentOfTwo(this.video.videoWidth) && Tools.IsExponentOfTwo(this.video.videoHeight))
+            ) {
                 this.wrapU = Texture.WRAP_ADDRESSMODE;
                 this.wrapV = Texture.WRAP_ADDRESSMODE;
             } else {
@@ -47,61 +109,71 @@
                 this._generateMipMaps = false;
             }
 
-            if (urls) {
-                this.video.addEventListener("canplay", () => {
-                    if (this._texture === undefined){ 
-                      this._createTexture();
-                    }
-                });
-                urls.forEach(url => {
-                    var source = document.createElement("source");
-                    source.src = url;
-                    this.video.appendChild(source);
-                });
-            } else {
-                this._createTexture();
-            }
-
-            this._lastUpdate = Tools.Now;
-        }
+            this._texture = this._engine.createDynamicTexture(
+                this.video.videoWidth,
+                this.video.videoHeight,
+                this._generateMipMaps,
+                this._samplingMode
+            );
+            this._texture.width;
 
-        private __setTextureReady(): void {
-            if (this._texture) {
-                this._texture.isReady = true;
-            }
-        }
+            this.updateInternalTexture();
 
-        private _createTexture(): void {
-            this._texture = this._engine.createDynamicTexture(this.video.videoWidth, this.video.videoHeight, this._generateMipMaps, this._samplingMode);
+            this._texture.isReady = true;
+        };
 
-            if (this._autoLaunch) {
-                this._autoLaunch = false;
-                this.video.play();
+        private reset = (): void => {
+            if (this._texture == null) {
+                return;
             }
-            this._setTextureReady = this.__setTextureReady.bind(this);
-            this.video.addEventListener("playing", this._setTextureReady);
-        }
-
+            this._texture.dispose();
+            this._texture = null;
+        };
 
         public _rebuild(): void {
             this.update();
         }
 
-        public update(): boolean {
-            var now = Tools.Now;
+        public update(): void {
+            if (!this.autoUpdateTexture) {
+                // Expecting user to call `updateTexture` manually
+                return;
+            }
+
+            this.updateTexture(true);
+        }
 
-            if (now - this._lastUpdate < 15 || this.video.readyState !== this.video.HAVE_ENOUGH_DATA) {
-                return false;
+        public updateTexture(isVisible: boolean): void {
+            if (!isVisible) {
+                return;
+            }
+            if (this.video.paused) {
+                return;
+            }
+            this.updateInternalTexture();
+        }
+
+        protected updateInternalTexture = (e?: Event): void => {
+            if (this._texture == null || !this._texture.isReady) {
+                return;
+            }
+            if (this.video.readyState < this.video.HAVE_CURRENT_DATA) {
+                return;
             }
 
-            this._lastUpdate = now;
             this._engine.updateVideoTexture(this._texture, this.video, this._invertY);
-            return true;
+        };
+
+        public updateURL(url: string): void {
+            this.video.src = url;
         }
 
         public dispose(): void {
             super.dispose();
-            this.video.removeEventListener("playing", this._setTextureReady);
+            this.video.removeEventListener("canplay", this.createInternalTexture);
+            this.video.removeEventListener("paused", this.updateInternalTexture);
+            this.video.removeEventListener("seeked", this.updateInternalTexture);
+            this.video.removeEventListener("emptied", this.reset);
         }
 
         public static CreateFromWebCam(scene: Scene, onReady: (videoTexture: VideoTexture) => void, constraints: {