1 /**************************************************************************** 2 Copyright (c) 2013-2014 Chukong Technologies Inc. 3 4 http://www.cocos2d-x.org 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 22 THE SOFTWARE. 23 ****************************************************************************/ 24 25 /** 26 * @class 27 * @extends ccui.Widget 28 * @brief Displays a video file. 29 * 30 * @note VideoPlayer displays a video file based on DOM element 31 * VideoPlayer will be rendered above all other graphical elements. 32 * 33 * @property {String} path - The video path 34 */ 35 ccui.VideoPlayer = ccui.Widget.extend(/** @lends ccui.VideoPlayer# */{ 36 37 _played: false, 38 _playing: false, 39 _stopped: true, 40 41 ctor: function(path){ 42 ccui.Widget.prototype.ctor.call(this); 43 this._EventList = {}; 44 if(path) 45 this.setURL(path); 46 }, 47 48 _createRenderCmd: function(){ 49 return new ccui.VideoPlayer.RenderCmd(this); 50 }, 51 52 /** 53 * Set the video address 54 * Automatically replace extname 55 * All supported video formats will be added to the video 56 * @param {String} address 57 */ 58 setURL: function(address){ 59 this._renderCmd.updateURL(address); 60 }, 61 62 /** 63 * Get the video path 64 * @returns {String} 65 */ 66 getURL: function() { 67 return this._renderCmd._url; 68 }, 69 70 /** 71 * Play the video 72 */ 73 play: function(){ 74 var self = this, 75 video = this._renderCmd._video; 76 if(video){ 77 this._played = true; 78 video.pause(); 79 if(this._stopped !== false || this._playing !== false || this._played !== true) 80 video.currentTime = 0; 81 if(ccui.VideoPlayer._polyfill.autoplayAfterOperation){ 82 setTimeout(function(){ 83 video.play(); 84 self._playing = true; 85 self._stopped = false; 86 }, 20); 87 }else{ 88 video.play(); 89 this._playing = true; 90 this._stopped = false; 91 } 92 } 93 }, 94 95 /** 96 * Pause the video 97 */ 98 pause: function(){ 99 var video = this._renderCmd._video; 100 if(video && this._playing === true && this._stopped === false){ 101 video.pause(); 102 this._playing = false; 103 } 104 }, 105 106 /** 107 * Resume the video 108 */ 109 resume: function(){ 110 if(this._stopped === false && this._playing === false && this._played === true){ 111 this.play(); 112 } 113 }, 114 115 /** 116 * Stop the video 117 */ 118 stop: function(){ 119 var self = this, 120 video = this._renderCmd._video; 121 if(video){ 122 video.pause(); 123 video.currentTime = 0; 124 this._playing = false; 125 this._stopped = true; 126 } 127 128 setTimeout(function(){ 129 self._dispatchEvent(ccui.VideoPlayer.EventType.STOPPED); 130 }, 0); 131 }, 132 /** 133 * Jump to the specified point in time 134 * @param {Number} sec 135 */ 136 seekTo: function(sec){ 137 var video = this._renderCmd._video; 138 if(video){ 139 video.currentTime = sec; 140 if(ccui.VideoPlayer._polyfill.autoplayAfterOperation && this.isPlaying()){ 141 setTimeout(function(){ 142 video.play(); 143 }, 20); 144 } 145 } 146 }, 147 148 /** 149 * Whether the video is playing 150 * @returns {boolean} 151 */ 152 isPlaying: function(){ 153 if(ccui.VideoPlayer._polyfill.autoplayAfterOperation && this._playing){ 154 setTimeout(function(){ 155 video.play(); 156 }, 20); 157 } 158 return this._playing; 159 }, 160 161 /** 162 * Whether to keep the aspect ratio 163 */ 164 setKeepAspectRatioEnabled: function(enable){ 165 cc.log("On the web is always keep the aspect ratio"); 166 }, 167 isKeepAspectRatioEnabled: function(){ 168 return false; 169 }, 170 171 /** 172 * Set whether the full screen 173 * May appear inconsistent in different browsers 174 * @param {boolean} enable 175 */ 176 setFullScreenEnabled: function(enable){ 177 var video = this._renderCmd._video; 178 if(video){ 179 if(enable) 180 cc.screen.requestFullScreen(video); 181 else 182 cc.screen.exitFullScreen(video); 183 } 184 }, 185 186 /** 187 * Determine whether already full screen 188 */ 189 isFullScreenEnabled: function(){ 190 cc.log("Can't know status"); 191 }, 192 193 /** 194 * The binding event 195 * @param {ccui.VideoPlayer.EventType} event 196 * @param {Function} callback 197 */ 198 setEventListener: function(event, callback){ 199 this._EventList[event] = callback; 200 }, 201 202 /** 203 * Delete events 204 * @param {ccui.VideoPlayer.EventType} event 205 */ 206 removeEventListener: function(event){ 207 this._EventList[event] = null; 208 }, 209 210 _dispatchEvent: function(event) { 211 var callback = this._EventList[event]; 212 if (callback) 213 callback.call(this, this, this._renderCmd._video.src); 214 }, 215 216 /** 217 * Trigger playing events 218 */ 219 onPlayEvent: function(){ 220 var list = this._EventList[ccui.VideoPlayer.EventType.PLAYING]; 221 if(list) 222 for(var i=0; i<list.length; i++) 223 list[i].call(this, this, this._renderCmd._video.src); 224 }, 225 226 //_createCloneInstance: function(){}, 227 //_copySpecialProperties: function(){}, 228 229 setContentSize: function(w, h){ 230 ccui.Widget.prototype.setContentSize.call(this, w, h); 231 if(h === undefined){ 232 h = w.height; 233 w = w.width; 234 } 235 this._renderCmd.changeSize(w, h); 236 }, 237 238 cleanup: function(){ 239 this._renderCmd.removeDom(); 240 this.stopAllActions(); 241 this.unscheduleAllCallbacks(); 242 } 243 244 }); 245 246 /** 247 * The VideoPlayer support list of events 248 * @type {{PLAYING: string, PAUSED: string, STOPPED: string, COMPLETED: string}} 249 */ 250 ccui.VideoPlayer.EventType = { 251 PLAYING: "play", 252 PAUSED: "pause", 253 STOPPED: "stop", 254 COMPLETED: "complete" 255 }; 256 257 (function(video){ 258 /** 259 * Adapter various machines 260 * @devicePixelRatio Whether you need to consider devicePixelRatio calculated position 261 * @event To get the data using events 262 */ 263 video._polyfill = { 264 devicePixelRatio: false, 265 event: "canplay", 266 canPlayType: [] 267 }; 268 269 (function(){ 270 /** 271 * Some old browser only supports Theora encode video 272 * But native does not support this encode, 273 * so it is best to provide mp4 and webm or ogv file 274 */ 275 var dom = document.createElement("video"); 276 if(dom.canPlayType("video/ogg")){ 277 video._polyfill.canPlayType.push(".ogg"); 278 video._polyfill.canPlayType.push(".ogv"); 279 } 280 if(dom.canPlayType("video/mp4")) 281 video._polyfill.canPlayType.push(".mp4"); 282 if(dom.canPlayType("video/webm")) 283 video._polyfill.canPlayType.push(".webm"); 284 })(); 285 286 if(cc.sys.OS_IOS === cc.sys.os){ 287 video._polyfill.devicePixelRatio = true; 288 video._polyfill.event = "progress"; 289 } 290 if(cc.sys.browserType === cc.sys.BROWSER_TYPE_FIREFOX){ 291 video._polyfill.autoplayAfterOperation = true; 292 } 293 294 var style = document.createElement("style"); 295 style.innerHTML = ".cocosVideo:-moz-full-screen{transform:matrix(1,0,0,1,0,0) !important;}" + 296 ".cocosVideo:full-screen{transform:matrix(1,0,0,1,0,0) !important;}" + 297 ".cocosVideo:-webkit-full-screen{transform:matrix(1,0,0,1,0,0) !important;}"; 298 document.head.appendChild(style); 299 300 })(ccui.VideoPlayer); 301 302 (function(polyfill){ 303 ccui.VideoPlayer.RenderCmd = function(node){ 304 cc.Node.CanvasRenderCmd.call(this, node); 305 this._listener = null; 306 this._url = ""; 307 this.initStyle(); 308 }; 309 310 var proto = ccui.VideoPlayer.RenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype); 311 proto.constructor = ccui.VideoPlayer.RenderCmd; 312 313 proto.visit = function(){ 314 var self = this, 315 container = cc.container, 316 eventManager = cc.eventManager; 317 if(this._node._visible){ 318 container.appendChild(this._video); 319 if(this._listener === null) 320 this._listener = cc.eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () { 321 self.resize(); 322 }); 323 }else{ 324 var hasChild = false; 325 if('contains' in container) { 326 hasChild = container.contains(this._video); 327 }else { 328 hasChild = container.compareDocumentPosition(this._video) % 16; 329 } 330 if(hasChild) 331 container.removeChild(this._video); 332 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 333 eventManager._removeListenerInVector(list, this._listener); 334 this._listener = null; 335 } 336 this.updateStatus(); 337 }; 338 339 proto.updateStatus = function(){ 340 polyfill.devicePixelRatio = cc.view.isRetinaEnabled(); 341 var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag; 342 if(locFlag & flags.transformDirty){ 343 //update the transform 344 this.transform(this.getParentRenderCmd(), true); 345 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 346 this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag; 347 } 348 }; 349 350 proto.resize = function(view){ 351 view = view || cc.view; 352 var node = this._node, 353 eventManager = cc.eventManager; 354 if(node._parent && node._visible) 355 this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY); 356 else{ 357 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 358 eventManager._removeListenerInVector(list, this._listener); 359 this._listener = null; 360 } 361 }; 362 363 proto.updateMatrix = function(t, scaleX, scaleY){ 364 var node = this._node; 365 if(polyfill.devicePixelRatio){ 366 var dpr = window.devicePixelRatio; 367 scaleX = scaleX / dpr; 368 scaleY = scaleY / dpr; 369 } 370 if(this._loaded === false) return; 371 var cw = node._contentSize.width, 372 ch = node._contentSize.height; 373 var a = t.a * scaleX, 374 b = t.b, 375 c = t.c, 376 d = t.d * scaleY, 377 tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX, 378 ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY; 379 var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")"; 380 this._video.style["transform"] = matrix; 381 this._video.style["-webkit-transform"] = matrix; 382 }; 383 384 proto.updateURL = function(path){ 385 var source, video, hasChild, container, extname; 386 var node = this._node; 387 388 if (this._url == path) 389 return; 390 391 this._url = path; 392 393 if(cc.loader.resPath && !/^http/.test(path)) 394 path = cc.path.join(cc.loader.resPath, path); 395 396 hasChild = false; 397 container = cc.container; 398 if('contains' in container) { 399 hasChild = container.contains(this._video); 400 }else { 401 hasChild = container.compareDocumentPosition(this._video) % 16; 402 } 403 if(hasChild) 404 container.removeChild(this._video); 405 406 this._video = document.createElement("video"); 407 video = this._video; 408 this.bindEvent(); 409 var self = this; 410 411 var cb = function(){ 412 if(self._loaded == true) 413 return; 414 self._loaded = true; 415 self.changeSize(); 416 self.setDirtyFlag(cc.Node._dirtyFlags.transformDirty); 417 video.removeEventListener(polyfill.event, cb); 418 video.currentTime = 0; 419 video.style["visibility"] = "visible"; 420 //IOS does not display video images 421 video.play(); 422 if(!node._played){ 423 video.pause(); 424 video.currentTime = 0; 425 } 426 }; 427 video.addEventListener(polyfill.event, cb); 428 429 //video.controls = "controls"; 430 video.preload = "metadata"; 431 video.style["visibility"] = "hidden"; 432 this._loaded = false; 433 node._played = false; 434 node._playing = false; 435 node._stopped = true; 436 this.initStyle(); 437 this.visit(); 438 439 source = document.createElement("source"); 440 source.src = path; 441 video.appendChild(source); 442 443 extname = cc.path.extname(path); 444 for(var i=0; i<polyfill.canPlayType.length; i++){ 445 if(extname !== polyfill.canPlayType[i]){ 446 source = document.createElement("source"); 447 source.src = path.replace(extname, polyfill.canPlayType[i]); 448 video.appendChild(source); 449 } 450 } 451 }; 452 453 proto.bindEvent = function(){ 454 var self = this, 455 node = this._node, 456 video = this._video; 457 //binding event 458 video.addEventListener("ended", function(){ 459 node._renderCmd.updateMatrix(self._worldTransform, cc.view._scaleX, cc.view._scaleY); 460 node._playing = false; 461 node._dispatchEvent(ccui.VideoPlayer.EventType.COMPLETED); 462 }); 463 video.addEventListener("play", function(){ 464 node._dispatchEvent(ccui.VideoPlayer.EventType.PLAYING); 465 }); 466 video.addEventListener("pause", function(){ 467 node._dispatchEvent(ccui.VideoPlayer.EventType.PAUSED); 468 }); 469 }; 470 471 proto.initStyle = function(){ 472 if(!this._video) return; 473 var video = this._video; 474 video.style.position = "absolute"; 475 video.style.bottom = "0px"; 476 video.style.left = "0px"; 477 video.className = "cocosVideo"; 478 }; 479 480 proto.changeSize = function(w, h){ 481 var contentSize = this._node._contentSize; 482 w = w || contentSize.width; 483 h = h || contentSize.height; 484 var video = this._video; 485 if(video){ 486 if(w !== 0) 487 video.width = w; 488 if(h !== 0) 489 video.height = h; 490 } 491 }; 492 493 proto.removeDom = function(){ 494 var video = this._video; 495 if(video){ 496 var hasChild = false; 497 if('contains' in cc.container) { 498 hasChild = cc.container.contains(video); 499 }else { 500 hasChild = cc.container.compareDocumentPosition(video) % 16; 501 } 502 if(hasChild) 503 cc.container.removeChild(video); 504 } 505 }; 506 507 })(ccui.VideoPlayer._polyfill);