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);