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 onEnter: function(){ 245 ccui.Widget.prototype.onEnter.call(this); 246 var list = ccui.VideoPlayer.elements; 247 if(list.indexOf(this) === -1) 248 list.push(this); 249 }, 250 251 onExit: function(){ 252 ccui.Widget.prototype.onExit.call(this); 253 var list = ccui.VideoPlayer.elements; 254 var index = list.indexOf(this); 255 if(index !== -1) 256 list.splice(index, 1); 257 } 258 259 }); 260 261 // VideoHTMLElement list 262 ccui.VideoPlayer.elements = []; 263 ccui.VideoPlayer.pauseElements = []; 264 265 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () { 266 var list = ccui.VideoPlayer.elements; 267 for(var node, i=0; i<list.length; i++){ 268 node = list[i]; 269 if(list[i]._playing){ 270 node.pause(); 271 ccui.VideoPlayer.pauseElements.push(node); 272 } 273 } 274 }); 275 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () { 276 var list = ccui.VideoPlayer.pauseElements; 277 var node = list.pop(); 278 while(node){ 279 node.play(); 280 node = list.pop(); 281 } 282 }); 283 284 /** 285 * The VideoPlayer support list of events 286 * @type {{PLAYING: string, PAUSED: string, STOPPED: string, COMPLETED: string}} 287 */ 288 ccui.VideoPlayer.EventType = { 289 PLAYING: "play", 290 PAUSED: "pause", 291 STOPPED: "stop", 292 COMPLETED: "complete" 293 }; 294 295 (function(video){ 296 /** 297 * Adapter various machines 298 * @devicePixelRatio Whether you need to consider devicePixelRatio calculated position 299 * @event To get the data using events 300 */ 301 video._polyfill = { 302 devicePixelRatio: false, 303 event: "canplay", 304 canPlayType: [] 305 }; 306 307 (function(){ 308 /** 309 * Some old browser only supports Theora encode video 310 * But native does not support this encode, 311 * so it is best to provide mp4 and webm or ogv file 312 */ 313 var dom = document.createElement("video"); 314 if(dom.canPlayType("video/ogg")){ 315 video._polyfill.canPlayType.push(".ogg"); 316 video._polyfill.canPlayType.push(".ogv"); 317 } 318 if(dom.canPlayType("video/mp4")) 319 video._polyfill.canPlayType.push(".mp4"); 320 if(dom.canPlayType("video/webm")) 321 video._polyfill.canPlayType.push(".webm"); 322 })(); 323 324 if(cc.sys.OS_IOS === cc.sys.os){ 325 video._polyfill.devicePixelRatio = true; 326 video._polyfill.event = "progress"; 327 } 328 if(cc.sys.browserType === cc.sys.BROWSER_TYPE_FIREFOX){ 329 video._polyfill.autoplayAfterOperation = true; 330 } 331 332 var style = document.createElement("style"); 333 style.innerHTML = ".cocosVideo:-moz-full-screen{transform:matrix(1,0,0,1,0,0) !important;}" + 334 ".cocosVideo:full-screen{transform:matrix(1,0,0,1,0,0) !important;}" + 335 ".cocosVideo:-webkit-full-screen{transform:matrix(1,0,0,1,0,0) !important;}"; 336 document.head.appendChild(style); 337 338 })(ccui.VideoPlayer); 339 340 (function(polyfill){ 341 342 var RenderCmd = null; 343 if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) { 344 RenderCmd = cc.Node.WebGLRenderCmd; 345 } else { 346 RenderCmd = cc.Node.CanvasRenderCmd; 347 } 348 349 ccui.VideoPlayer.RenderCmd = function(node){ 350 RenderCmd.call(this, node); 351 this._listener = null; 352 this._url = ""; 353 this.initStyle(); 354 }; 355 356 var proto = ccui.VideoPlayer.RenderCmd.prototype = Object.create(RenderCmd.prototype); 357 proto.constructor = ccui.VideoPlayer.RenderCmd; 358 359 proto.visit = function(){ 360 var self = this, 361 container = cc.container, 362 eventManager = cc.eventManager; 363 if(this._node._visible){ 364 container.appendChild(this._video); 365 if(this._listener === null) 366 this._listener = cc.eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () { 367 self.resize(); 368 }); 369 }else{ 370 var hasChild = false; 371 if('contains' in container) { 372 hasChild = container.contains(this._video); 373 }else { 374 hasChild = container.compareDocumentPosition(this._video) % 16; 375 } 376 if(hasChild) 377 container.removeChild(this._video); 378 eventManager.removeListener(this._listener); 379 this._listener = null; 380 } 381 this.updateStatus(); 382 }; 383 384 proto.transform = function (parentCmd, recursive) { 385 this.originTransform(parentCmd, recursive); 386 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 387 }; 388 389 proto.updateStatus = function(){ 390 polyfill.devicePixelRatio = cc.view.isRetinaEnabled(); 391 var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag; 392 if(locFlag & flags.transformDirty){ 393 //update the transform 394 this.transform(this.getParentRenderCmd(), true); 395 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 396 this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag; 397 } 398 399 if (locFlag & flags.orderDirty) { 400 this._dirtyFlag = this._dirtyFlag & flags.orderDirty ^ this._dirtyFlag; 401 } 402 }; 403 404 proto.resize = function(view){ 405 view = view || cc.view; 406 var node = this._node, 407 eventManager = cc.eventManager; 408 if(node._parent && node._visible) 409 this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY); 410 else{ 411 eventManager.removeListener(this._listener); 412 this._listener = null; 413 } 414 }; 415 416 proto.updateMatrix = function(t, scaleX, scaleY){ 417 var node = this._node; 418 if(polyfill.devicePixelRatio){ 419 var dpr = cc.view.getDevicePixelRatio(); 420 scaleX = scaleX / dpr; 421 scaleY = scaleY / dpr; 422 } 423 if(this._loaded === false) return; 424 var cw = node._contentSize.width, 425 ch = node._contentSize.height; 426 var a = t.a * scaleX, 427 b = t.b, 428 c = t.c, 429 d = t.d * scaleY, 430 tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX, 431 ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY; 432 var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")"; 433 this._video.style["transform"] = matrix; 434 this._video.style["-webkit-transform"] = matrix; 435 }; 436 437 proto.updateURL = function(path){ 438 var source, video, hasChild, container, extname; 439 var node = this._node; 440 441 if (this._url == path) 442 return; 443 444 this._url = path; 445 446 if(cc.loader.resPath && !/^http/.test(path)) 447 path = cc.path.join(cc.loader.resPath, path); 448 449 hasChild = false; 450 container = cc.container; 451 if('contains' in container) { 452 hasChild = container.contains(this._video); 453 }else { 454 hasChild = container.compareDocumentPosition(this._video) % 16; 455 } 456 if(hasChild) 457 container.removeChild(this._video); 458 459 this._video = document.createElement("video"); 460 video = this._video; 461 this.bindEvent(); 462 var self = this; 463 464 var cb = function(){ 465 if(self._loaded == true) 466 return; 467 self._loaded = true; 468 self.changeSize(); 469 self.setDirtyFlag(cc.Node._dirtyFlags.transformDirty); 470 video.removeEventListener(polyfill.event, cb); 471 video.currentTime = 0; 472 video.style["visibility"] = "visible"; 473 //IOS does not display video images 474 video.play(); 475 if(!node._played){ 476 video.pause(); 477 video.currentTime = 0; 478 } 479 }; 480 video.addEventListener(polyfill.event, cb); 481 482 //video.controls = "controls"; 483 video.preload = "metadata"; 484 video.style["visibility"] = "hidden"; 485 this._loaded = false; 486 node._played = false; 487 node._playing = false; 488 node._stopped = true; 489 this.initStyle(); 490 this.visit(); 491 492 source = document.createElement("source"); 493 source.src = path; 494 video.appendChild(source); 495 496 extname = cc.path.extname(path); 497 for(var i=0; i<polyfill.canPlayType.length; i++){ 498 if(extname !== polyfill.canPlayType[i]){ 499 source = document.createElement("source"); 500 source.src = path.replace(extname, polyfill.canPlayType[i]); 501 video.appendChild(source); 502 } 503 } 504 }; 505 506 proto.bindEvent = function(){ 507 var self = this, 508 node = this._node, 509 video = this._video; 510 //binding event 511 video.addEventListener("ended", function(){ 512 node._renderCmd.updateMatrix(self._worldTransform, cc.view._scaleX, cc.view._scaleY); 513 node._playing = false; 514 node._dispatchEvent(ccui.VideoPlayer.EventType.COMPLETED); 515 }); 516 video.addEventListener("play", function(){ 517 node._dispatchEvent(ccui.VideoPlayer.EventType.PLAYING); 518 }); 519 video.addEventListener("pause", function(){ 520 node._dispatchEvent(ccui.VideoPlayer.EventType.PAUSED); 521 }); 522 }; 523 524 proto.initStyle = function(){ 525 if(!this._video) return; 526 var video = this._video; 527 video.style.position = "absolute"; 528 video.style.bottom = "0px"; 529 video.style.left = "0px"; 530 video.className = "cocosVideo"; 531 }; 532 533 proto.changeSize = function(w, h){ 534 var contentSize = this._node._contentSize; 535 w = w || contentSize.width; 536 h = h || contentSize.height; 537 var video = this._video; 538 if(video){ 539 if(w !== 0) 540 video.width = w; 541 if(h !== 0) 542 video.height = h; 543 } 544 }; 545 546 proto.removeDom = function(){ 547 var video = this._video; 548 if(video){ 549 var hasChild = false; 550 if('contains' in cc.container) { 551 hasChild = cc.container.contains(video); 552 }else { 553 hasChild = cc.container.compareDocumentPosition(video) % 16; 554 } 555 if(hasChild) 556 cc.container.removeChild(video); 557 } 558 }; 559 560 })(ccui.VideoPlayer._polyfill);