1 /** 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2016 DeNA Co., Ltd. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 /// <reference path="base.js"/> 26 /// <reference path="config.js"/> 27 /// <reference path="display_object.js"/> 28 /// <reference path="rectangle.js"/> 29 /// <reference path="ticker.js"/> 30 31 /** 32 * A class that encapsulates an HTMLVideoElement object. 33 * @param {HTMLVideoElement} video 34 * @extends {createjs.DisplayObject} 35 * @implements {EventListener} 36 * @constructor 37 */ 38 createjs.Video = function(video) { 39 /// <param type="HTMLVideoElement" name="value"/> 40 createjs.DisplayObject.call(this); 41 42 /** 43 * The <video> element to be rendered. 44 * @type {HTMLVideoElement} 45 * @private 46 */ 47 this.video_ = video; 48 49 // Initialize the bounding box with the video size. 50 this.setBoundingBox(0, 0, video.videoWidth, video.videoHeight); 51 }; 52 createjs.inherits('Video', createjs.Video, createjs.DisplayObject); 53 54 /** 55 * Whether the associated <video> element starts over when it reaches its end. 56 * @type {boolean} 57 * @private 58 */ 59 createjs.Video.prototype.loop_ = false; 60 61 /** 62 * Whether this object is listening 'ended' events. 63 * @type {boolean} 64 * @private 65 */ 66 createjs.Video.prototype.ended_ = false; 67 68 /** 69 * The position of the associated <video> element when it is rendered, in 70 * seconds. 71 * @type {number} 72 * @private 73 */ 74 createjs.Video.prototype.lastTime_ = 0; 75 76 /** 77 * The last time when the associated <video> element is rendered. 78 * @type {number} 79 * @private 80 */ 81 createjs.Video.prototype.runTime_ = -1; 82 83 /** 84 * The renderer that draws this object. 85 * @type {createjs.Renderer} 86 * @private 87 */ 88 createjs.Video.prototype.output_ = null; 89 90 /** 91 * The <canvas> element to be rendered. 92 * @type {HTMLCanvasElement} 93 * @private 94 */ 95 createjs.Video.prototype.canvas_ = null; 96 97 /** 98 * The <canvas> element to be rendered. 99 * @type {CanvasRenderingContext2D} 100 * @private 101 */ 102 createjs.Video.prototype.context_ = null; 103 104 /** 105 * Starts playing this video. This method does not only start playing the 106 * <video> element attached to this object but it also resets the time-stamp to 107 * force drawing this object. 108 * @param {number} time 109 * @param {boolean} loop 110 * @const 111 */ 112 createjs.Video.prototype.play = function(time, loop) { 113 var video = this.video_; 114 if (time) { 115 this.lastTime_ = time; 116 video.currentTime = time; 117 } 118 this.loop_ = !!loop; 119 // Play this <video> element only when the host browser can play <video> 120 // elements inline. (Set both the 'playsinline' attribute so Mobile Safari on 121 // iOS can play the <video> element inline and set the 'webkit-playsinline' 122 // attribute so standalone-mode web apps can, respectively.) 123 // earlier.) 124 if (createjs.Config.canPlayInline()) { 125 video.setAttribute('muted', ''); 126 video.setAttribute('playsinline', ''); 127 video.setAttribute('webkit-playsinline', ''); 128 video.loop = this.loop_; 129 if (!this.loop_) { 130 if (!this.ended_) { 131 this.ended_ = true; 132 video.addEventListener('ended', this, false); 133 } 134 } 135 video.play(); 136 } else { 137 video.load(); 138 this.runTime_ = createjs.Ticker.getRunTime(); 139 } 140 }; 141 142 /** 143 * Stops playing this video. 144 * @const 145 */ 146 createjs.Video.prototype.stop = function() { 147 var video = this.video_; 148 if (this.ended_) { 149 this.ended_ = false; 150 video.removeEventListener('ended', this, false); 151 } 152 video.pause(); 153 }; 154 155 /** @override */ 156 createjs.Video.prototype.removeAllChildren = function(opt_destroy) { 157 /// <param type="boolean" optional="true" name="opt_destroy"/> 158 this.handleDetach(); 159 }; 160 161 /** @override */ 162 createjs.Video.prototype.handleDetach = function() { 163 if (this.canvas_) { 164 if (this.output_) { 165 this.output_.uncache(this.canvas_); 166 } 167 this.context_ = null; 168 this.canvas_ = null; 169 } else { 170 if (this.output_) { 171 this.output_.uncache(this.video_); 172 } 173 } 174 this.stop(); 175 this.video_ = null; 176 }; 177 178 /** @override */ 179 createjs.Video.prototype.paintObject = function(renderer) { 180 /// <param type="createjs.Renderer" name="renderer"/> 181 var video = this.video_; 182 // Draw this <video> element only when it has enough data to avoid a warning 183 // "WebGL: INVALID_VALUE: texImage2D: no video". 184 /** @enum {number} */ 185 var ReadyState = { 186 HAVE_NOTHING: 0, 187 HAVE_METADATA: 1, 188 HAVE_CURRENT_DATA: 2, 189 HAVE_FUTURE_DATA: 3, 190 HAVE_ENOUGH_DATA: 4 191 }; 192 if (video.readyState < ReadyState.HAVE_ENOUGH_DATA) { 193 return; 194 } 195 // Synchronize the video size with the bounding-box size of this object first 196 // time when this object is rendered. (The video size is reliable only when 197 // its readyState property is >= HAVE_METADATA (1).) 198 if (!this.getBoxWidth()) { 199 this.setBoundingBox(0, 0, video.videoWidth, video.videoHeight); 200 } 201 if (!this.output_) { 202 this.output_ = renderer; 203 } 204 var current = video.currentTime; 205 if (this.runTime_ >= 0) { 206 // Advance the position of the associated <video> element manually to get 207 // the image at that position if the host browser cannot play <video> 208 // elements inline, i.e. Mobile Safari on iOS 9 or earlier. 209 var runTime = createjs.Ticker.getRunTime(); 210 current += (runTime - this.runTime_) * 0.001; 211 this.runTime_ = runTime; 212 var duration = video.duration; 213 if (current >= duration) { 214 if (this.loop_) { 215 current = current % duration; 216 } else { 217 current = duration; 218 } 219 } 220 video.currentTime = current; 221 } 222 if (!renderer.getExtensions()) { 223 // Create a placeholder <canvas> element and copy the current frame to it if 224 // this renderer cannot render <video> elements. (The WebGLRenderer object 225 // cannot renderer <video> elements on IE11, which does not allow creating 226 // WebGLTexture objects with <video> elements.) 227 var canvas = this.canvas_; 228 if (!canvas) { 229 canvas = createjs.createCanvas(); 230 canvas.width = this.getBoxWidth(); 231 canvas.height = this.getBoxHeight(); 232 this.canvas_ = canvas; 233 this.context_ = createjs.getRenderingContext2D(canvas); 234 current = -1; 235 } 236 if (this.lastTime_ != current) { 237 this.lastTime_ = current; 238 this.context_.drawImage(video, 0, 0); 239 canvas.dirty_ = 1; 240 } 241 renderer.drawCanvas(canvas, 0, 0, this.getBoxWidth(), this.getBoxHeight()); 242 } else { 243 if (this.lastTime_ != current) { 244 this.lastTime_ = current; 245 video.dirty_ = 1; 246 } 247 renderer.drawVideo(video, 0, 0, this.getBoxWidth(), this.getBoxHeight()); 248 } 249 }; 250 251 /** @override */ 252 createjs.Video.prototype.handleEvent = function(event) { 253 /// <param type="Event" name="event"/> 254 var type = event.type; 255 if (type == 'ended') { 256 this.stop(); 257 this.dispatchNotification('complete'); 258 } 259 }; 260 261 // Export the createjs.Video object to the global namespace. 262 createjs.exportObject('createjs.Video', createjs.Video, { 263 'play': createjs.Video.prototype.play, 264 'stop': createjs.Video.prototype.stop 265 }); 266