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 ccui.VideoPlayer.RenderCmd = function(node){ 342 cc.Node.CanvasRenderCmd.call(this, node); 343 this._listener = null; 344 this._url = ""; 345 this.initStyle(); 346 }; 347 348 var proto = ccui.VideoPlayer.RenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype); 349 proto.constructor = ccui.VideoPlayer.RenderCmd; 350 351 proto.visit = function(){ 352 var self = this, 353 container = cc.container, 354 eventManager = cc.eventManager; 355 if(this._node._visible){ 356 container.appendChild(this._video); 357 if(this._listener === null) 358 this._listener = cc.eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () { 359 self.resize(); 360 }); 361 }else{ 362 var hasChild = false; 363 if('contains' in container) { 364 hasChild = container.contains(this._video); 365 }else { 366 hasChild = container.compareDocumentPosition(this._video) % 16; 367 } 368 if(hasChild) 369 container.removeChild(this._video); 370 eventManager.removeListener(this._listener); 371 this._listener = null; 372 } 373 this.updateStatus(); 374 }; 375 376 proto.updateStatus = function(){ 377 polyfill.devicePixelRatio = cc.view.isRetinaEnabled(); 378 var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag; 379 if(locFlag & flags.transformDirty){ 380 //update the transform 381 this.transform(this.getParentRenderCmd(), true); 382 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 383 this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag; 384 } 385 }; 386 387 proto.resize = function(view){ 388 view = view || cc.view; 389 var node = this._node, 390 eventManager = cc.eventManager; 391 if(node._parent && node._visible) 392 this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY); 393 else{ 394 eventManager.removeListener(this._listener); 395 this._listener = null; 396 } 397 }; 398 399 proto.updateMatrix = function(t, scaleX, scaleY){ 400 var node = this._node; 401 if(polyfill.devicePixelRatio){ 402 var dpr = window.devicePixelRatio; 403 scaleX = scaleX / dpr; 404 scaleY = scaleY / dpr; 405 } 406 if(this._loaded === false) return; 407 var cw = node._contentSize.width, 408 ch = node._contentSize.height; 409 var a = t.a * scaleX, 410 b = t.b, 411 c = t.c, 412 d = t.d * scaleY, 413 tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX, 414 ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY; 415 var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")"; 416 this._video.style["transform"] = matrix; 417 this._video.style["-webkit-transform"] = matrix; 418 }; 419 420 proto.updateURL = function(path){ 421 var source, video, hasChild, container, extname; 422 var node = this._node; 423 424 if (this._url == path) 425 return; 426 427 this._url = path; 428 429 if(cc.loader.resPath && !/^http/.test(path)) 430 path = cc.path.join(cc.loader.resPath, path); 431 432 hasChild = false; 433 container = cc.container; 434 if('contains' in container) { 435 hasChild = container.contains(this._video); 436 }else { 437 hasChild = container.compareDocumentPosition(this._video) % 16; 438 } 439 if(hasChild) 440 container.removeChild(this._video); 441 442 this._video = document.createElement("video"); 443 video = this._video; 444 this.bindEvent(); 445 var self = this; 446 447 var cb = function(){ 448 if(self._loaded == true) 449 return; 450 self._loaded = true; 451 self.changeSize(); 452 self.setDirtyFlag(cc.Node._dirtyFlags.transformDirty); 453 video.removeEventListener(polyfill.event, cb); 454 video.currentTime = 0; 455 video.style["visibility"] = "visible"; 456 //IOS does not display video images 457 video.play(); 458 if(!node._played){ 459 video.pause(); 460 video.currentTime = 0; 461 } 462 }; 463 video.addEventListener(polyfill.event, cb); 464 465 //video.controls = "controls"; 466 video.preload = "metadata"; 467 video.style["visibility"] = "hidden"; 468 this._loaded = false; 469 node._played = false; 470 node._playing = false; 471 node._stopped = true; 472 this.initStyle(); 473 this.visit(); 474 475 source = document.createElement("source"); 476 source.src = path; 477 video.appendChild(source); 478 479 extname = cc.path.extname(path); 480 for(var i=0; i<polyfill.canPlayType.length; i++){ 481 if(extname !== polyfill.canPlayType[i]){ 482 source = document.createElement("source"); 483 source.src = path.replace(extname, polyfill.canPlayType[i]); 484 video.appendChild(source); 485 } 486 } 487 }; 488 489 proto.bindEvent = function(){ 490 var self = this, 491 node = this._node, 492 video = this._video; 493 //binding event 494 video.addEventListener("ended", function(){ 495 node._renderCmd.updateMatrix(self._worldTransform, cc.view._scaleX, cc.view._scaleY); 496 node._playing = false; 497 node._dispatchEvent(ccui.VideoPlayer.EventType.COMPLETED); 498 }); 499 video.addEventListener("play", function(){ 500 node._dispatchEvent(ccui.VideoPlayer.EventType.PLAYING); 501 }); 502 video.addEventListener("pause", function(){ 503 node._dispatchEvent(ccui.VideoPlayer.EventType.PAUSED); 504 }); 505 }; 506 507 proto.initStyle = function(){ 508 if(!this._video) return; 509 var video = this._video; 510 video.style.position = "absolute"; 511 video.style.bottom = "0px"; 512 video.style.left = "0px"; 513 video.className = "cocosVideo"; 514 }; 515 516 proto.changeSize = function(w, h){ 517 var contentSize = this._node._contentSize; 518 w = w || contentSize.width; 519 h = h || contentSize.height; 520 var video = this._video; 521 if(video){ 522 if(w !== 0) 523 video.width = w; 524 if(h !== 0) 525 video.height = h; 526 } 527 }; 528 529 proto.removeDom = function(){ 530 var video = this._video; 531 if(video){ 532 var hasChild = false; 533 if('contains' in cc.container) { 534 hasChild = cc.container.contains(video); 535 }else { 536 hasChild = cc.container.compareDocumentPosition(video) % 16; 537 } 538 if(hasChild) 539 cc.container.removeChild(video); 540 } 541 }; 542 543 })(ccui.VideoPlayer._polyfill);