438 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
		
		
			
		
	
	
			438 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
|  | (function( Popcorn ) { | ||
|  | 
 | ||
|  |   // combines calls of two function calls into one
 | ||
|  |   var combineFn = function( first, second ) { | ||
|  | 
 | ||
|  |     first = first || Popcorn.nop; | ||
|  |     second = second || Popcorn.nop; | ||
|  | 
 | ||
|  |     return function() { | ||
|  | 
 | ||
|  |       first.apply( this, arguments ); | ||
|  |       second.apply( this, arguments ); | ||
|  |     }; | ||
|  |   }; | ||
|  | 
 | ||
|  |   //  ID string matching
 | ||
|  |   var rIdExp  = /^(#([\w\-\_\.]+))$/; | ||
|  | 
 | ||
|  |   Popcorn.player = function( name, player ) { | ||
|  | 
 | ||
|  |     // return early if a player already exists under this name
 | ||
|  |     if ( Popcorn[ name ] ) { | ||
|  | 
 | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     player = player || {}; | ||
|  | 
 | ||
|  |     var playerFn = function( target, src, options ) { | ||
|  | 
 | ||
|  |       options = options || {}; | ||
|  | 
 | ||
|  |       // List of events
 | ||
|  |       var date = new Date() / 1000, | ||
|  |           baselineTime = date, | ||
|  |           currentTime = 0, | ||
|  |           readyState = 0, | ||
|  |           volume = 1, | ||
|  |           muted = false, | ||
|  |           events = {}, | ||
|  | 
 | ||
|  |           // The container div of the resource
 | ||
|  |           container = typeof target === "string" ? Popcorn.dom.find( target ) : target, | ||
|  |           basePlayer = {}, | ||
|  |           timeout, | ||
|  |           popcorn; | ||
|  | 
 | ||
|  |       if ( !Object.prototype.__defineGetter__ ) { | ||
|  | 
 | ||
|  |         basePlayer = container || document.createElement( "div" ); | ||
|  |       } | ||
|  | 
 | ||
|  |       // copies a div into the media object
 | ||
|  |       for( var val in container ) { | ||
|  | 
 | ||
|  |         // don't copy properties if using container as baseplayer
 | ||
|  |         if ( val in basePlayer ) { | ||
|  | 
 | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if ( typeof container[ val ] === "object" ) { | ||
|  | 
 | ||
|  |           basePlayer[ val ] = container[ val ]; | ||
|  |         } else if ( typeof container[ val ] === "function" ) { | ||
|  | 
 | ||
|  |           basePlayer[ val ] = (function( value ) { | ||
|  | 
 | ||
|  |             // this is a stupid ugly kludgy hack in honour of Safari
 | ||
|  |             // in Safari a NodeList is a function, not an object
 | ||
|  |             if ( "length" in container[ value ] && !container[ value ].call ) { | ||
|  | 
 | ||
|  |               return container[ value ]; | ||
|  |             } else { | ||
|  | 
 | ||
|  |               return function() { | ||
|  | 
 | ||
|  |                 return container[ value ].apply( container, arguments ); | ||
|  |               }; | ||
|  |             } | ||
|  |           }( val )); | ||
|  |         } else { | ||
|  | 
 | ||
|  |           Popcorn.player.defineProperty( basePlayer, val, { | ||
|  |             get: (function( value ) { | ||
|  | 
 | ||
|  |               return function() { | ||
|  | 
 | ||
|  |                 return container[ value ]; | ||
|  |               }; | ||
|  |             }( val )), | ||
|  |             set: Popcorn.nop, | ||
|  |             configurable: true | ||
|  |           }); | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       var timeupdate = function() { | ||
|  | 
 | ||
|  |         date = new Date() / 1000; | ||
|  | 
 | ||
|  |         if ( !basePlayer.paused ) { | ||
|  | 
 | ||
|  |           basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime ); | ||
|  |           basePlayer.dispatchEvent( "timeupdate" ); | ||
|  |           timeout = setTimeout( timeupdate, 10 ); | ||
|  |         } | ||
|  | 
 | ||
|  |         baselineTime = date; | ||
|  |       }; | ||
|  | 
 | ||
|  |       basePlayer.play = function() { | ||
|  | 
 | ||
|  |         this.paused = false; | ||
|  | 
 | ||
|  |         if ( basePlayer.readyState >= 4 ) { | ||
|  | 
 | ||
|  |           baselineTime = new Date() / 1000; | ||
|  |           basePlayer.dispatchEvent( "play" ); | ||
|  |           timeupdate(); | ||
|  |         } | ||
|  |       }; | ||
|  | 
 | ||
|  |       basePlayer.pause = function() { | ||
|  | 
 | ||
|  |         this.paused = true; | ||
|  |         basePlayer.dispatchEvent( "pause" ); | ||
|  |       }; | ||
|  | 
 | ||
|  |       Popcorn.player.defineProperty( basePlayer, "currentTime", { | ||
|  |         get: function() { | ||
|  | 
 | ||
|  |           return currentTime; | ||
|  |         }, | ||
|  |         set: function( val ) { | ||
|  | 
 | ||
|  |           // make sure val is a number
 | ||
|  |           currentTime = +val; | ||
|  |           basePlayer.dispatchEvent( "timeupdate" ); | ||
|  | 
 | ||
|  |           return currentTime; | ||
|  |         }, | ||
|  |         configurable: true | ||
|  |       }); | ||
|  | 
 | ||
|  |       Popcorn.player.defineProperty( basePlayer, "volume", { | ||
|  |         get: function() { | ||
|  | 
 | ||
|  |           return volume; | ||
|  |         }, | ||
|  |         set: function( val ) { | ||
|  | 
 | ||
|  |           // make sure val is a number
 | ||
|  |           volume = +val; | ||
|  |           basePlayer.dispatchEvent( "volumechange" ); | ||
|  |           return volume; | ||
|  |         }, | ||
|  |         configurable: true | ||
|  |       }); | ||
|  | 
 | ||
|  |       Popcorn.player.defineProperty( basePlayer, "muted", { | ||
|  |         get: function() { | ||
|  | 
 | ||
|  |           return muted; | ||
|  |         }, | ||
|  |         set: function( val ) { | ||
|  | 
 | ||
|  |           // make sure val is a number
 | ||
|  |           muted = +val; | ||
|  |           basePlayer.dispatchEvent( "volumechange" ); | ||
|  |           return muted; | ||
|  |         }, | ||
|  |         configurable: true | ||
|  |       }); | ||
|  | 
 | ||
|  |       Popcorn.player.defineProperty( basePlayer, "readyState", { | ||
|  |         get: function() { | ||
|  | 
 | ||
|  |           return readyState; | ||
|  |         }, | ||
|  |         set: function( val ) { | ||
|  | 
 | ||
|  |           readyState = val; | ||
|  |           return readyState; | ||
|  |         }, | ||
|  |         configurable: true | ||
|  |       }); | ||
|  | 
 | ||
|  |       // Adds an event listener to the object
 | ||
|  |       basePlayer.addEventListener = function( evtName, fn ) { | ||
|  | 
 | ||
|  |         if ( !events[ evtName ] ) { | ||
|  | 
 | ||
|  |           events[ evtName ] = []; | ||
|  |         } | ||
|  | 
 | ||
|  |         events[ evtName ].push( fn ); | ||
|  |         return fn; | ||
|  |       }; | ||
|  | 
 | ||
|  |       // Removes an event listener from the object
 | ||
|  |       basePlayer.removeEventListener = function( evtName, fn ) { | ||
|  | 
 | ||
|  |         var i, | ||
|  |             listeners = events[ evtName ]; | ||
|  | 
 | ||
|  |         if ( !listeners ){ | ||
|  | 
 | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // walk backwards so we can safely splice
 | ||
|  |         for ( i = events[ evtName ].length - 1; i >= 0; i-- ) { | ||
|  | 
 | ||
|  |           if( fn === listeners[ i ] ) { | ||
|  | 
 | ||
|  |             listeners.splice(i, 1); | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         return fn; | ||
|  |       }; | ||
|  | 
 | ||
|  |       // Can take event object or simple string
 | ||
|  |       basePlayer.dispatchEvent = function( oEvent ) { | ||
|  | 
 | ||
|  |         var evt, | ||
|  |             self = this, | ||
|  |             eventInterface, | ||
|  |             eventName = oEvent.type; | ||
|  | 
 | ||
|  |         // A string was passed, create event object
 | ||
|  |         if ( !eventName ) { | ||
|  | 
 | ||
|  |           eventName = oEvent; | ||
|  |           eventInterface  = Popcorn.events.getInterface( eventName ); | ||
|  | 
 | ||
|  |           if ( eventInterface ) { | ||
|  | 
 | ||
|  |             evt = document.createEvent( eventInterface ); | ||
|  |             evt.initEvent( eventName, true, true, window, 1 ); | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         if ( events[ eventName ] ) { | ||
|  | 
 | ||
|  |           for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) { | ||
|  | 
 | ||
|  |             events[ eventName ][ i ].call( self, evt, self ); | ||
|  |           } | ||
|  |         } | ||
|  |       }; | ||
|  | 
 | ||
|  |       // Attempt to get src from playerFn parameter
 | ||
|  |       basePlayer.src = src || ""; | ||
|  |       basePlayer.duration = 0; | ||
|  |       basePlayer.paused = true; | ||
|  |       basePlayer.ended = 0; | ||
|  | 
 | ||
|  |       options && options.events && Popcorn.forEach( options.events, function( val, key ) { | ||
|  | 
 | ||
|  |         basePlayer.addEventListener( key, val, false ); | ||
|  |       }); | ||
|  | 
 | ||
|  |       // true and undefined returns on canPlayType means we should attempt to use it,
 | ||
|  |       // false means we cannot play this type
 | ||
|  |       if ( player._canPlayType( container.nodeName, src ) !== false ) { | ||
|  | 
 | ||
|  |         if ( player._setup ) { | ||
|  | 
 | ||
|  |           player._setup.call( basePlayer, options ); | ||
|  |         } else { | ||
|  | 
 | ||
|  |           // there is no setup, which means there is nothing to load
 | ||
|  |           basePlayer.readyState = 4; | ||
|  |           basePlayer.dispatchEvent( "loadedmetadata" ); | ||
|  |           basePlayer.dispatchEvent( "loadeddata" ); | ||
|  |           basePlayer.dispatchEvent( "canplaythrough" ); | ||
|  |         } | ||
|  |       } else { | ||
|  | 
 | ||
|  |         // Asynchronous so that users can catch this event
 | ||
|  |         setTimeout( function() { | ||
|  |           basePlayer.dispatchEvent( "error" ); | ||
|  |         }, 0 ); | ||
|  |       } | ||
|  | 
 | ||
|  |       popcorn = new Popcorn.p.init( basePlayer, options ); | ||
|  | 
 | ||
|  |       if ( player._teardown ) { | ||
|  | 
 | ||
|  |         popcorn.destroy = combineFn( popcorn.destroy, function() { | ||
|  | 
 | ||
|  |           player._teardown.call( basePlayer, options ); | ||
|  |         }); | ||
|  |       } | ||
|  | 
 | ||
|  |       return popcorn; | ||
|  |     }; | ||
|  | 
 | ||
|  |     playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop; | ||
|  | 
 | ||
|  |     Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn; | ||
|  |   }; | ||
|  | 
 | ||
|  |   Popcorn.player.registry = {}; | ||
|  | 
 | ||
|  |   Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { | ||
|  | 
 | ||
|  |     object.__defineGetter__( description, options.get || Popcorn.nop ); | ||
|  |     object.__defineSetter__( description, options.set || Popcorn.nop ); | ||
|  |   }; | ||
|  | 
 | ||
|  |   // player queue is to help players queue things like play and pause
 | ||
|  |   // HTML5 video's play and pause are asynch, but do fire in sequence
 | ||
|  |   // play() should really mean "requestPlay()" or "queuePlay()" and
 | ||
|  |   // stash a callback that will play the media resource when it's ready to be played
 | ||
|  |   Popcorn.player.playerQueue = function() { | ||
|  | 
 | ||
|  |     var _queue = [], | ||
|  |         _running = false; | ||
|  | 
 | ||
|  |     return { | ||
|  |       next: function() { | ||
|  | 
 | ||
|  |         _running = false; | ||
|  |         _queue.shift(); | ||
|  |         _queue[ 0 ] && _queue[ 0 ](); | ||
|  |       }, | ||
|  |       add: function( callback ) { | ||
|  | 
 | ||
|  |         _queue.push(function() { | ||
|  | 
 | ||
|  |           _running = true; | ||
|  |           callback && callback(); | ||
|  |         }); | ||
|  | 
 | ||
|  |         // if there is only one item on the queue, start it
 | ||
|  |         !_running && _queue[ 0 ](); | ||
|  |       } | ||
|  |     }; | ||
|  |   }; | ||
|  | 
 | ||
|  |   // Popcorn.smart will attempt to find you a wrapper or player. If it can't do that,
 | ||
|  |   // it will default to using an HTML5 video in the target.
 | ||
|  |   Popcorn.smart = function( target, src, options ) { | ||
|  |     var node = typeof target === "string" ? Popcorn.dom.find( target ) : target, | ||
|  |         i, srci, j, media, mediaWrapper, popcorn, srcLength,  | ||
|  |         // We leave HTMLVideoElement and HTMLAudioElement wrappers out
 | ||
|  |         // of the mix, since we'll default to HTML5 video if nothing
 | ||
|  |         // else works.  Waiting on #1254 before we add YouTube to this.
 | ||
|  |         wrappers = "HTMLYouTubeVideoElement HTMLVimeoVideoElement HTMLSoundCloudAudioElement HTMLNullVideoElement".split(" "); | ||
|  | 
 | ||
|  |     if ( !node ) { | ||
|  |       Popcorn.error( "Specified target `" + target + "` was not found." ); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     // If our src is not an array, create an array of one.
 | ||
|  |     src = typeof src === "string" ? [ src ] : src; | ||
|  | 
 | ||
|  |     // Loop through each src, and find the first playable.
 | ||
|  |     for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { | ||
|  |       srci = src[ i ]; | ||
|  | 
 | ||
|  |       // See if we can use a wrapper directly, if not, try players.
 | ||
|  |       for ( j = 0; j < wrappers.length; j++ ) { | ||
|  |         mediaWrapper = Popcorn[ wrappers[ j ] ]; | ||
|  |         if ( mediaWrapper && mediaWrapper._canPlaySrc( srci ) === "probably" ) { | ||
|  |           media = mediaWrapper( node ); | ||
|  |           popcorn = Popcorn( media, options ); | ||
|  |           // Set src, but not until after we return the media so the caller
 | ||
|  |           // can get error events, if any.
 | ||
|  |           setTimeout( function() { | ||
|  |             media.src = srci; | ||
|  |           }, 0 ); | ||
|  |           return popcorn; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       // No wrapper can play this, check players.
 | ||
|  |       for ( var key in Popcorn.player.registry ) { | ||
|  |         if ( Popcorn.player.registry.hasOwnProperty( key ) ) { | ||
|  |           if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, srci ) ) { | ||
|  |             // Popcorn.smart( player, src, /* options */ )
 | ||
|  |             return Popcorn[ key ]( node, srci, options ); | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // If we don't have any players or wrappers that can handle this,
 | ||
|  |     // Default to using HTML5 video.  Similar to the HTMLVideoElement
 | ||
|  |     // wrapper, we put a video in the div passed to us via:
 | ||
|  |     // Popcorn.smart( div, src, options )
 | ||
|  |     var videoHTML, | ||
|  |         videoElement, | ||
|  |         videoID = Popcorn.guid( "popcorn-video-" ), | ||
|  |         videoHTMLContainer = document.createElement( "div" ); | ||
|  | 
 | ||
|  |     videoHTMLContainer.style.width = "100%"; | ||
|  |     videoHTMLContainer.style.height = "100%"; | ||
|  | 
 | ||
|  |     // If we only have one source, do not bother with source elements.
 | ||
|  |     // This means we don't have the IE9 hack,
 | ||
|  |     // and we can properly listen to error events.
 | ||
|  |     // That way an error event can be told to backup to Flash if it fails.
 | ||
|  |     if ( src.length === 1 ) { | ||
|  |       videoElement = document.createElement( "video" ); | ||
|  |       videoElement.id = videoID; | ||
|  |       node.appendChild( videoElement ); | ||
|  |       setTimeout( function() { | ||
|  |         // Hack to decode html characters like & to &
 | ||
|  |         var decodeDiv = document.createElement( "div" ); | ||
|  |         decodeDiv.innerHTML = src[ 0 ]; | ||
|  | 
 | ||
|  |         videoElement.src = decodeDiv.firstChild.nodeValue; | ||
|  |       }, 0 ); | ||
|  |       return Popcorn( '#' + videoID, options ); | ||
|  |     } | ||
|  | 
 | ||
|  |     node.appendChild( videoHTMLContainer ); | ||
|  |     // IE9 doesn't like dynamic creation of source elements on <video>
 | ||
|  |     // so we do it in one shot via innerHTML.
 | ||
|  |     videoHTML = '<video id="' +  videoID + '" preload=auto autobuffer>'; | ||
|  |     for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { | ||
|  |       videoHTML += '<source src="' + src[ i ] + '">'; | ||
|  |     } | ||
|  |     videoHTML += "</video>"; | ||
|  |     videoHTMLContainer.innerHTML = videoHTML; | ||
|  | 
 | ||
|  |     if ( options && options.events && options.events.error ) { | ||
|  |       node.addEventListener( "error", options.events.error, false ); | ||
|  |     } | ||
|  |     return Popcorn( '#' + videoID, options ); | ||
|  |   }; | ||
|  | })( Popcorn ); |