Sfoglia il codice sorgente

Merge pull request #3567 from OrangeLV/video-texture

More flexible Video Texture
David Catuhe 7 anni fa
parent
commit
1d9e1eeba2
1 ha cambiato i file con 212 aggiunte e 93 eliminazioni
  1. 212 93
      src/Materials/Textures/babylon.videoTexture.ts

+ 212 - 93
src/Materials/Textures/babylon.videoTexture.ts

@@ -1,44 +1,127 @@
 module BABYLON {
 module BABYLON {
+    /**
+     * Settings for finer control over video usage
+     */
+    export interface VideoTextureSettings {
+        /**
+         * Applies `autoplay` to video, if specified
+         */
+        autoPlay?: boolean;
+
+        /**
+         * Applies `loop` to video, if specified
+         */
+        loop?: boolean;
+
+        /**
+         * Automatically updates internal texture from video at every frame in the render loop
+         */
+        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 {
     export class VideoTexture extends Texture {
-        public video: HTMLVideoElement;
+        /**
+         * 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;
 
 
-        private _autoLaunch = true;
-        private _lastUpdate: number;
-        private _generateMipMaps: boolean
-        private _setTextureReady: () => void;
+        private _generateMipMaps: boolean;
         private _engine: Engine;
         private _engine: Engine;
 
 
         /**
         /**
          * Creates a video texture.
          * 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 {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} 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 {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 {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);
             super(null, scene, !generateMipMaps, invertY);
 
 
-            var urls: Nullable<string[]> = null;
-            this.name = name;
+            this._engine = 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.wrapU = Texture.WRAP_ADDRESSMODE;
                 this.wrapV = Texture.WRAP_ADDRESSMODE;
                 this.wrapV = Texture.WRAP_ADDRESSMODE;
             } else {
             } else {
@@ -47,110 +130,146 @@
                 this._generateMipMaps = false;
                 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._texture = this._engine.createDynamicTexture(
+                this.video.videoWidth,
+                this.video.videoHeight,
+                this._generateMipMaps,
+                this._samplingMode
+            );
+            this._texture.width;
 
 
-            this._lastUpdate = Tools.Now;
-        }
-
-        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;
+        };
 
 
+        /**
+         * Internal method to initiate `update`.
+         */
         public _rebuild(): void {
         public _rebuild(): void {
             this.update();
             this.update();
         }
         }
 
 
-        public update(): boolean {
-            var now = Tools.Now;
+        /**
+         * 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;
+            }
 
 
-            if (now - this._lastUpdate < 15 || this.video.readyState !== this.video.HAVE_ENOUGH_DATA) {
-                return false;
+            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 othervise.
+         */
+        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);
             this._engine.updateVideoTexture(this._texture, this.video, this._invertY);
-            return true;
+        };
+
+        /**
+         * Change video content. Changing video instance or setting multiple urls (as in constructor) is not supported.
+         * @param url New url.
+         */
+        public updateURL(url: string): void {
+            this.video.src = url;
         }
         }
 
 
         public dispose(): void {
         public dispose(): void {
             super.dispose();
             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: {
-            minWidth: number,
-            maxWidth: number,
-            minHeight: number,
-            maxHeight: number,
-            deviceId: string
-        }): void {
+        public static CreateFromWebCam(
+            scene: Scene,
+            onReady: (videoTexture: VideoTexture) => void,
+            constraints: {
+                minWidth: number;
+                maxWidth: number;
+                minHeight: number;
+                maxHeight: number;
+                deviceId: string;
+            }
+        ): void {
             var video = document.createElement("video");
             var video = document.createElement("video");
             var constraintsDeviceId;
             var constraintsDeviceId;
             if (constraints && constraints.deviceId) {
             if (constraints && constraints.deviceId) {
                 constraintsDeviceId = {
                 constraintsDeviceId = {
-                    exact: constraints.deviceId
-                }
+                    exact: constraints.deviceId,
+                };
             }
             }
 
 
-            navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
+            navigator.getUserMedia =
+                navigator.getUserMedia ||
+                navigator.webkitGetUserMedia ||
+                navigator.mozGetUserMedia ||
+                navigator.msGetUserMedia;
             window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
             window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
 
 
             if (navigator.getUserMedia) {
             if (navigator.getUserMedia) {
-                navigator.getUserMedia({
-                    video: {
-                        deviceId: constraintsDeviceId,
-                        width: {
-                            min: (constraints && constraints.minWidth) || 256,
-                            max: (constraints && constraints.maxWidth) || 640
+                navigator.getUserMedia(
+                    {
+                        video: {
+                            deviceId: constraintsDeviceId,
+                            width: {
+                                min: (constraints && constraints.minWidth) || 256,
+                                max: (constraints && constraints.maxWidth) || 640,
+                            },
+                            height: {
+                                min: (constraints && constraints.minHeight) || 256,
+                                max: (constraints && constraints.maxHeight) || 480,
+                            },
                         },
                         },
-                        height: {
-                            min: (constraints && constraints.minHeight) || 256,
-                            max: (constraints && constraints.maxHeight) || 480
+                    },
+                    (stream: any) => {
+                        if (video.mozSrcObject !== undefined) {
+                            // hack for Firefox < 19
+                            video.mozSrcObject = stream;
+                        } else {
+                            video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
                         }
                         }
-                    }
-                }, (stream: any) => {
-
-                    if (video.mozSrcObject !== undefined) { // hack for Firefox < 19
-                        video.mozSrcObject = stream;
-                    } else {
-                        video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
-                    }
 
 
-                    video.play();
+                        video.play();
 
 
-                    if (onReady) {
-                        onReady(new VideoTexture("video", video, scene, true, true));
+                        if (onReady) {
+                            onReady(new VideoTexture("video", video, scene, true, true));
+                        }
+                    },
+                    function(e: MediaStreamError) {
+                        Tools.Error(e.name);
                     }
                     }
-                }, function (e: MediaStreamError) {
-                    Tools.Error(e.name);
-                });
+                );
             }
             }
         }
         }
     }
     }