(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