// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== sc_require('views/controls'); sc_require('views/mini_controls'); /** @class Renders a videoView using different technologies like HTML5 video tag, quicktime and flash. This view wraps the different technologies so you can use one standard and simple API to play videos. You can specify and array with the order of how the technologies will degrad depending on availability. For example you can set degradeList to be ['html5', 'flash'] and it will load your video in a video tag if the technology is available otherwise flash and if neither of the technologies are available it will show a message saying that your machine needs to install one of this technologies. @extends SC.View @since SproutCore 1.1 */ SC.VideoView = SC.View.extend({ /** Video view className. @property {String} */ classNames: 'sc-video-view', /** Properties that trigger a re render of the view. If the value changes, it means that the video url changed. @property {Array} */ displayProperties: ['value', 'shouldAutoResize'], /** Reference to the video object once is created. @property {Object} */ videoObject:null, /** Array containing the technologies and the order to load them depending availability @property {Array} */ degradeList: ['html5','quicktime', 'flash'], /** Current time in secs @property {Number} */ currentTime: 0, /** Duration in secs @property {Number} */ duration: 0, //video duration in secs /** Volume. The value should be between 0 and 1 @property {Number} */ volume:0, //volume value from 0 to 1 /** Tells you if the video is paused or not. @property {Boolean} */ paused: YES, //is the video paused /** Tells you if the video is loaded. @property {Boolean} */ loaded: NO, //has the video loaded /** Indicates if the video has reached the end @property {Boolean} */ ended: NO, //did the video finished playing /** Indicates if the video is ready to be played. @property {Boolean} */ canPlay: NO, //can the video be played /** Width of the video in pixels. @property {Number} */ videoWidth:0, /** Width of the video in pixels. @property {Number} */ videoHeight:0, /** Flag to enable captions if available. @property {Boolean} */ captionsEnabled: NO, loadedTimeRanges:[], //loaded bits poster: null, /** Formatted currentTime. (00:00) @property {String} */ time: function(){ var currentTime=this.get('currentTime'), totaltimeInSecs = this.get('duration'); var formattedTime = this._addZeros(Math.floor(currentTime/60))+':'+this._addZeros(Math.floor(currentTime%60))+"/"+this._addZeros(Math.floor(totaltimeInSecs/60))+':'+this._addZeros(Math.floor(totaltimeInSecs%60)); return formattedTime; }.property('currentTime', 'duration').cacheable(), /** Renders the appropiate HTML according for the technology to use. @param {SC.RenderContext} context the render context @param {Boolean} firstTime YES if this is creating a layer @returns {void} */ render: function(context, firstTime) { var i, j, listLen, pluginsLen, id = SC.guidFor(this); if(firstTime){ for(i=0, listLen = this.degradeList.length; i'); this.loaded='html5'; return; } break; case "quicktime": if(SC.browser.msie){ context.push(' '+ ' '); } context.push(''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''); this.loaded='quicktime'; return; case "flash": var flashURL= sc_static('videoCanvas.swf'); var movieURL = this.get('value'); if (!movieURL) return; if(movieURL.indexOf('http:')==-1){ movieURL=location.protocol+'//'+location.host+movieURL; } if(movieURL.indexOf('?')!=-1){ movieURL=movieURL.substring(0, movieURL.indexOf('?')); } movieURL = encodeURI(movieURL); context.push(''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ' '+ ''+ ''); this.loaded='flash'; SC.VideoView.addToVideoFlashViews(this); return; default: context.push('video is not supported by your browser'); return; } } } }, valueObserver:function(){ this.set('currentTime', 0); this.set('duration', 0); this.set('volume', 0); this.set('paused', YES); this.set('loaded', NO); this.set('ended', NO); this.set('canPlay', NO); this.set('loadedTimeRanges', []); this.replaceLayer(); }.observes('value'), /** This function is called everytime the frame changes. This is done to get the right video dimensions for HTML5 video tag. @returns {void} */ frameDidChange: function() { if(this.loaded==="html5"){ var fr= this.get('frame'), elem = this.$('video'); elem.attr('width', fr.width); elem.attr('height', fr.height); } }.observes('frame'), /** In didCreateLayer we add DOM events for video tag or quicktime. @returns {void} */ didCreateLayer :function(){ if(this.loaded==="html5"){ this.addVideoDOMEvents(); this.frameDidChange(); } if(this.loaded==="quicktime"){ this.addQTDOMEvents(); } }, didAppendToDocument :function(){ if(this.loaded==="quicktime"){ this.addQTDOMEvents(); } }, /** Adds all the neccesary video DOM elements. @returns {void} */ addVideoDOMEvents: function() { var videoElem, view=this; videoElem = this.$('video')[0]; this.set('videoObject', videoElem); SC.Event.add(videoElem, 'durationchange', this, function () { SC.RunLoop.begin(); view.set('duration', videoElem.duration); SC.RunLoop.end(); }) ; SC.Event.add(videoElem, 'timeupdate', this, function () { SC.RunLoop.begin(); view.set('currentTime', videoElem.currentTime); SC.RunLoop.end(); }) ; SC.Event.add(videoElem, 'loadstart', this, function () { SC.RunLoop.begin(); this.updateVideoElementLoadedTimeRanges(videoElem); view.set('volume', videoElem.volume); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'play', this, function () { SC.RunLoop.begin(); view.set('paused', NO); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'pause', this, function () { SC.RunLoop.begin(); view.set('paused', YES); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'loadedmetadata', this, function () { SC.RunLoop.begin(); view.set('videoWidth', videoElem.videoWidth); view.set('videoHeight', videoElem.videoHeight); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'canplay', this, function () { SC.RunLoop.begin(); this.updateVideoElementLoadedTimeRanges(videoElem); view.set('canPlay', YES); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'ended', this, function () { SC.RunLoop.begin(); view.set('ended', YES); SC.RunLoop.end(); }); SC.Event.add(videoElem, 'progress', this, function (e) { SC.RunLoop.begin(); this.updateVideoElementLoadedTimeRanges(videoElem); try{ var trackCount=view.GetTrackCount(),i; for(i=1; i<=trackCount;i++){ if("Closed Caption"===this.GetTrackType(i)){ view._closedCaptionTrackIndex=i; } } }catch(f){} //view.set('loadedData', ev.loaded); //console.log('progress '+ev.loaded+","+ev.total ); SC.RunLoop.end(); }); // SC.Event.add(videoElem, 'suspend', this, function () { // SC.RunLoop.begin(); // console.log('suspend'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'load', this, function () { // SC.RunLoop.begin(); // console.log('load'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'abort', this, function () { // SC.RunLoop.begin(); // console.log('abort'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'error', this, function () { // SC.RunLoop.begin(); // console.log('error'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'loadend', this, function () { // SC.RunLoop.begin(); // console.log('loadend'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'emptied', this, function () { // SC.RunLoop.begin(); // console.log('emptied'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'stalled', this, function () { // SC.RunLoop.begin(); // console.log('stalled'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'loadeddata', this, function () { // SC.RunLoop.begin(); // console.log('loadeddata'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'waiting', this, function () { // SC.RunLoop.begin(); // console.log('waiting'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'playing', this, function () { // SC.RunLoop.begin(); // console.log('playing'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'canplaythrough', this, function () { // SC.RunLoop.begin(); // console.log('canplaythrough'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'seeking', this, function () { // SC.RunLoop.begin(); // console.log('seeking'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'seeked', this, function () { // SC.RunLoop.begin(); // console.log('seeked'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'ratechange', this, function () { // SC.RunLoop.begin(); // console.log('ratechange'); // SC.RunLoop.end(); // }); // SC.Event.add(videoElem, 'volumechange', this, function () { // SC.RunLoop.begin(); // console.log('volumechange'); // SC.RunLoop.end(); // }); }, updateVideoElementLoadedTimeRanges: function(videoElem) { if(!videoElem) videoElem = this.$('video')[0]; if(!this.loadedTimeRanges) this.loadedTimeRanges=[]; else this.loadedTimeRanges.length=0; for (var j=0, jLen = videoElem.buffered.length; j