--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/popcorn.js Wed Dec 07 12:07:12 2011 +0100
@@ -0,0 +1,2055 @@
+(function(global, document) {
+
+ // Popcorn.js does not support archaic browsers
+ if ( !document.addEventListener ) {
+ global.Popcorn = {
+ isSupported: false
+ };
+
+ var methods = ( "forEach extend effects error guid sizeOf isArray nop position disable enable destroy " +
+ "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
+ "timeUpdate plugin removePlugin compose effect parser xhr getJSONP getScript" ).split(/\s+/);
+
+ while( methods.length ) {
+ global.Popcorn[ methods.shift() ] = function() {};
+ }
+ return;
+ }
+
+ var
+
+ AP = Array.prototype,
+ OP = Object.prototype,
+
+ forEach = AP.forEach,
+ slice = AP.slice,
+ hasOwn = OP.hasOwnProperty,
+ toString = OP.toString,
+
+ // Copy global Popcorn (may not exist)
+ _Popcorn = global.Popcorn,
+
+ // ID string matching
+ rIdExp = /^(#([\w\-\_\.]+))$/,
+
+ // Ready fn cache
+ readyStack = [],
+ readyBound = false,
+ readyFired = false,
+
+ // Non-public internal data object
+ internal = {
+ events: {
+ hash: {},
+ apis: {}
+ }
+ },
+
+ // Non-public `requestAnimFrame`
+ // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame = (function(){
+ return global.requestAnimationFrame ||
+ global.webkitRequestAnimationFrame ||
+ global.mozRequestAnimationFrame ||
+ global.oRequestAnimationFrame ||
+ global.msRequestAnimationFrame ||
+ function( callback, element ) {
+ global.setTimeout( callback, 16 );
+ };
+ }()),
+
+ // Declare constructor
+ // Returns an instance object.
+ Popcorn = function( entity, options ) {
+ // Return new Popcorn object
+ return new Popcorn.p.init( entity, options || null );
+ };
+
+ // Popcorn API version, automatically inserted via build system.
+ Popcorn.version = "@VERSION";
+
+ // Boolean flag allowing a client to determine if Popcorn can be supported
+ Popcorn.isSupported = true;
+
+ // Instance caching
+ Popcorn.instances = [];
+
+ // Declare a shortcut (Popcorn.p) to and a definition of
+ // the new prototype for our Popcorn constructor
+ Popcorn.p = Popcorn.prototype = {
+
+ init: function( entity, options ) {
+
+ var matches;
+
+ // Supports Popcorn(function () { /../ })
+ // Originally proposed by Daniel Brooks
+
+ if ( typeof entity === "function" ) {
+
+ // If document ready has already fired
+ if ( document.readyState === "interactive" || document.readyState === "complete" ) {
+
+ entity( document, Popcorn );
+
+ return;
+ }
+ // Add `entity` fn to ready stack
+ readyStack.push( entity );
+
+ // This process should happen once per page load
+ if ( !readyBound ) {
+
+ // set readyBound flag
+ readyBound = true;
+
+ var DOMContentLoaded = function() {
+
+ readyFired = true;
+
+ // Remove global DOM ready listener
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // Execute all ready function in the stack
+ for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
+
+ readyStack[ i ].call( document, Popcorn );
+
+ }
+ // GC readyStack
+ readyStack = null;
+ };
+
+ // Register global DOM ready listener
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ }
+
+ return;
+ }
+
+ // Check if entity is a valid string id
+ matches = rIdExp.exec( entity );
+
+ // Get media element by id or object reference
+ this.media = matches && matches.length && matches[ 2 ] ?
+ document.getElementById( matches[ 2 ] ) :
+ entity;
+
+ // Create an audio or video element property reference
+ this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media;
+
+ // Register new instance
+ Popcorn.instances.push( this );
+
+ this.options = options || {};
+
+ this.isDestroyed = false;
+
+ this.data = {
+
+ // Allows disabling a plugin per instance
+ disabled: [],
+
+ // Stores DOM event queues by type
+ events: {},
+
+ // Stores Special event hooks data
+ hooks: {},
+
+ // Store track event history data
+ history: [],
+
+ // Stores ad-hoc state related data]
+ state: {
+ volume: this.media.volume
+ },
+
+ // Store track event object references by trackId
+ trackRefs: {},
+
+ // Playback track event queues
+ trackEvents: {
+ byStart: [{
+
+ start: -1,
+ end: -1
+ }],
+ byEnd: [{
+ start: -1,
+ end: -1
+ }],
+ animating: [],
+ startIndex: 0,
+ endIndex: 0,
+ previousUpdateTime: -1
+ }
+ };
+
+ // Wrap true ready check
+ var isReady = function( that ) {
+
+ var duration, videoDurationPlus, animate;
+
+ if ( that.media.readyState >= 2 ) {
+ // Adding padding to the front and end of the arrays
+ // this is so we do not fall off either end
+
+ duration = that.media.duration;
+ // Check for no duration info (NaN)
+ videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
+
+ Popcorn.addTrackEvent( that, {
+ start: videoDurationPlus,
+ end: videoDurationPlus
+ });
+
+ if ( that.options.frameAnimation ) {
+ // if Popcorn is created with frameAnimation option set to true,
+ // requestAnimFrame is used instead of "timeupdate" media event.
+ // This is for greater frame time accuracy, theoretically up to
+ // 60 frames per second as opposed to ~4 ( ~every 15-250ms)
+ animate = function () {
+
+ Popcorn.timeUpdate( that, {} );
+
+ that.trigger( "timeupdate" );
+
+ requestAnimFrame( animate );
+ };
+
+ requestAnimFrame( animate );
+
+ } else {
+
+ that.data.timeUpdateFunction = function( event ) {
+ Popcorn.timeUpdate( that, event );
+ };
+
+ if ( !that.isDestroyed ) {
+ that.media.addEventListener( "timeupdate", that.data.timeUpdateFunction, false );
+ }
+ }
+ } else {
+ global.setTimeout(function() {
+ isReady( that );
+ }, 1 );
+ }
+ };
+
+ isReady( this );
+
+ return this;
+ }
+ };
+
+ // Extend constructor prototype to instance prototype
+ // Allows chaining methods to instances
+ Popcorn.p.init.prototype = Popcorn.p;
+
+ Popcorn.forEach = function( obj, fn, context ) {
+
+ if ( !obj || !fn ) {
+ return {};
+ }
+
+ context = context || this;
+
+ var key, len;
+
+ // Use native whenever possible
+ if ( forEach && obj.forEach === forEach ) {
+ return obj.forEach( fn, context );
+ }
+
+ if ( toString.call( obj ) === "[object NodeList]" ) {
+ for ( key = 0, len = obj.length; key < len; key++ ) {
+ fn.call( context, obj[ key ], key, obj );
+ }
+ return obj;
+ }
+
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ fn.call( context, obj[ key ], key, obj );
+ }
+ }
+ return obj;
+ };
+
+ Popcorn.extend = function( obj ) {
+ var dest = obj, src = slice.call( arguments, 1 );
+
+ Popcorn.forEach( src, function( copy ) {
+ for ( var prop in copy ) {
+ dest[ prop ] = copy[ prop ];
+ }
+ });
+
+ return dest;
+ };
+
+
+ // A Few reusable utils, memoized onto Popcorn
+ Popcorn.extend( Popcorn, {
+ noConflict: function( deep ) {
+
+ if ( deep ) {
+ global.Popcorn = _Popcorn;
+ }
+
+ return Popcorn;
+ },
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+ guid: function( prefix ) {
+ Popcorn.guid.counter++;
+ return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
+ },
+ sizeOf: function( obj ) {
+ var size = 0;
+
+ for ( var prop in obj ) {
+ size++;
+ }
+
+ return size;
+ },
+ isArray: Array.isArray || function( array ) {
+ return toString.call( array ) === "[object Array]";
+ },
+
+ nop: function() {},
+
+ position: function( elem ) {
+
+ var clientRect = elem.getBoundingClientRect(),
+ bounds = {},
+ doc = elem.ownerDocument,
+ docElem = document.documentElement,
+ body = document.body,
+ clientTop, clientLeft, scrollTop, scrollLeft, top, left;
+
+ // Determine correct clientTop/Left
+ clientTop = docElem.clientTop || body.clientTop || 0;
+ clientLeft = docElem.clientLeft || body.clientLeft || 0;
+
+ // Determine correct scrollTop/Left
+ scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
+ scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
+
+ // Temp top/left
+ top = Math.ceil( clientRect.top + scrollTop - clientTop );
+ left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
+
+ for ( var p in clientRect ) {
+ bounds[ p ] = Math.round( clientRect[ p ] );
+ }
+
+ return Popcorn.extend({}, bounds, { top: top, left: left });
+ },
+
+ disable: function( instance, plugin ) {
+
+ var disabled = instance.data.disabled;
+
+ if ( disabled.indexOf( plugin ) === -1 ) {
+ disabled.push( plugin );
+ }
+
+ return instance;
+ },
+ enable: function( instance, plugin ) {
+
+ var disabled = instance.data.disabled,
+ index = disabled.indexOf( plugin );
+
+ if ( index > -1 ) {
+ disabled.splice( index, 1 );
+ }
+
+ return instance;
+ },
+ destroy: function( instance ) {
+ var events = instance.data.events,
+ singleEvent, item, fn;
+
+ // Iterate through all events and remove them
+ for ( item in events ) {
+ singleEvent = events[ item ];
+ for ( fn in singleEvent ) {
+ delete singleEvent[ fn ];
+ }
+ events[ item ] = null;
+ }
+
+ if ( !instance.isDestroyed ) {
+ instance.media.removeEventListener( "timeupdate", instance.data.timeUpdateFunction, false );
+ instance.isDestroyed = true;
+ }
+ }
+ });
+
+ // Memoized GUID Counter
+ Popcorn.guid.counter = 1;
+
+ // Factory to implement getters, setters and controllers
+ // as Popcorn instance methods. The IIFE will create and return
+ // an object with defined methods
+ Popcorn.extend(Popcorn.p, (function() {
+
+ var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
+ "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
+ ret = {};
+
+
+ // Build methods, store in object that is returned and passed to extend
+ Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
+
+ ret[ name ] = function( arg ) {
+
+ if ( typeof this.media[ name ] === "function" ) {
+
+ // Support for shorthanded play(n)/pause(n) jump to currentTime
+ // If arg is not null or undefined and called by one of the
+ // allowed shorthandable methods, then set the currentTime
+ // Supports time as seconds or SMPTE
+ if ( arg != null && /play|pause/.test( name ) ) {
+ this.media.currentTime = Popcorn.util.toSeconds( arg );
+ }
+
+ this.media[ name ]();
+
+ return this;
+ }
+
+
+ if ( arg != null ) {
+
+ this.media[ name ] = arg;
+
+ return this;
+ }
+
+ return this.media[ name ];
+ };
+ });
+
+ return ret;
+
+ })()
+ );
+
+ Popcorn.forEach( "enable disable".split(" "), function( method ) {
+ Popcorn.p[ method ] = function( plugin ) {
+ return Popcorn[ method ]( this, plugin );
+ };
+ });
+
+ Popcorn.extend(Popcorn.p, {
+
+ // Rounded currentTime
+ roundTime: function() {
+ return -~this.media.currentTime;
+ },
+
+ // Attach an event to a single point in time
+ exec: function( time, fn ) {
+
+ // Creating a one second track event with an empty end
+ Popcorn.addTrackEvent( this, {
+ start: time,
+ end: time + 1,
+ _running: false,
+ _natives: {
+ start: fn || Popcorn.nop,
+ end: Popcorn.nop,
+ type: "exec"
+ }
+ });
+
+ return this;
+ },
+
+ // Mute the calling media, optionally toggle
+ mute: function( toggle ) {
+
+ var event = toggle == null || toggle === true ? "muted" : "unmuted";
+
+ // If `toggle` is explicitly `false`,
+ // unmute the media and restore the volume level
+ if ( event === "unmuted" ) {
+ this.media.muted = false;
+ this.media.volume = this.data.state.volume;
+ }
+
+ // If `toggle` is either null or undefined,
+ // save the current volume and mute the media element
+ if ( event === "muted" ) {
+ this.data.state.volume = this.media.volume;
+ this.media.muted = true;
+ }
+
+ // Trigger either muted|unmuted event
+ this.trigger( event );
+
+ return this;
+ },
+
+ // Convenience method, unmute the calling media
+ unmute: function( toggle ) {
+
+ return this.mute( toggle == null ? false : !toggle );
+ },
+
+ // Get the client bounding box of an instance element
+ position: function() {
+ return Popcorn.position( this.media );
+ },
+
+ // Toggle a plugin's playback behaviour (on or off) per instance
+ toggle: function( plugin ) {
+ return Popcorn[ this.data.disabled.indexOf( plugin ) > -1 ? "enable" : "disable" ]( this, plugin );
+ },
+
+ // Set default values for plugin options objects per instance
+ defaults: function( plugin, defaults ) {
+
+ // If an array of default configurations is provided,
+ // iterate and apply each to this instance
+ if ( Popcorn.isArray( plugin ) ) {
+
+ Popcorn.forEach( plugin, function( obj ) {
+ for ( var name in obj ) {
+ this.defaults( name, obj[ name ] );
+ }
+ }, this );
+
+ return this;
+ }
+
+ if ( !this.options.defaults ) {
+ this.options.defaults = {};
+ }
+
+ if ( !this.options.defaults[ plugin ] ) {
+ this.options.defaults[ plugin ] = {};
+ }
+
+ Popcorn.extend( this.options.defaults[ plugin ], defaults );
+
+ return this;
+ }
+ });
+
+ Popcorn.Events = {
+ UIEvents: "blur focus focusin focusout load resize scroll unload",
+ MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
+ Events: "loadstart progress suspend emptied stalled play pause " +
+ "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
+ "seeking seeked timeupdate ended ratechange durationchange volumechange"
+ };
+
+ Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
+ Popcorn.Events.MouseEvents + " " +
+ Popcorn.Events.Events;
+
+ internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
+
+ // Privately compile events table at load time
+ (function( events, data ) {
+
+ var apis = internal.events.apiTypes,
+ eventsList = events.Natives.split( /\s+/g ),
+ idx = 0, len = eventsList.length, prop;
+
+ for( ; idx < len; idx++ ) {
+ data.hash[ eventsList[idx] ] = true;
+ }
+
+ apis.forEach(function( val, idx ) {
+
+ data.apis[ val ] = {};
+
+ var apiEvents = events[ val ].split( /\s+/g ),
+ len = apiEvents.length,
+ k = 0;
+
+ for ( ; k < len; k++ ) {
+ data.apis[ val ][ apiEvents[ k ] ] = true;
+ }
+ });
+ })( Popcorn.Events, internal.events );
+
+ Popcorn.events = {
+
+ isNative: function( type ) {
+ return !!internal.events.hash[ type ];
+ },
+ getInterface: function( type ) {
+
+ if ( !Popcorn.events.isNative( type ) ) {
+ return false;
+ }
+
+ var eventApi = internal.events,
+ apis = eventApi.apiTypes,
+ apihash = eventApi.apis,
+ idx = 0, len = apis.length, api, tmp;
+
+ for ( ; idx < len; idx++ ) {
+ tmp = apis[ idx ];
+
+ if ( apihash[ tmp ][ type ] ) {
+ api = tmp;
+ break;
+ }
+ }
+ return api;
+ },
+ // Compile all native events to single array
+ all: Popcorn.Events.Natives.split( /\s+/g ),
+ // Defines all Event handling static functions
+ fn: {
+ trigger: function( type, data ) {
+
+ var eventInterface, evt;
+ // setup checks for custom event system
+ if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
+
+ eventInterface = Popcorn.events.getInterface( type );
+
+ if ( eventInterface ) {
+
+ evt = document.createEvent( eventInterface );
+ evt.initEvent( type, true, true, global, 1 );
+
+ this.media.dispatchEvent( evt );
+
+ return this;
+ }
+
+ // Custom events
+ Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
+
+ obj.call( this, data );
+
+ }, this );
+
+ }
+
+ return this;
+ },
+ listen: function( type, fn ) {
+
+ var self = this,
+ hasEvents = true,
+ eventHook = Popcorn.events.hooks[ type ],
+ origType = type,
+ tmp;
+
+ if ( !this.data.events[ type ] ) {
+ this.data.events[ type ] = {};
+ hasEvents = false;
+ }
+
+ // Check and setup event hooks
+ if ( eventHook ) {
+
+ // Execute hook add method if defined
+ if ( eventHook.add ) {
+ eventHook.add.call( this, {}, fn );
+ }
+
+ // Reassign event type to our piggyback event type if defined
+ if ( eventHook.bind ) {
+ type = eventHook.bind;
+ }
+
+ // Reassign handler if defined
+ if ( eventHook.handler ) {
+ tmp = fn;
+
+ fn = function wrapper( event ) {
+ eventHook.handler.call( self, event, tmp );
+ };
+ }
+
+ // assume the piggy back event is registered
+ hasEvents = true;
+
+ // Setup event registry entry
+ if ( !this.data.events[ type ] ) {
+ this.data.events[ type ] = {};
+ // Toggle if the previous assumption was untrue
+ hasEvents = false;
+ }
+ }
+
+ // Register event and handler
+ this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
+
+ // only attach one event of any type
+ if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
+
+ this.media.addEventListener( type, function( event ) {
+
+ Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
+ if ( typeof obj === "function" ) {
+ obj.call( self, event );
+ }
+ });
+
+ }, false);
+ }
+ return this;
+ },
+ unlisten: function( type, fn ) {
+
+ if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
+
+ delete this.data.events[ type ][ fn ];
+
+ return this;
+ }
+
+ this.data.events[ type ] = null;
+
+ return this;
+ }
+ },
+ hooks: {
+ canplayall: {
+ bind: "canplaythrough",
+ add: function( event, callback ) {
+
+ var state = false;
+
+ if ( this.media.readyState ) {
+
+ callback.call( this, event );
+
+ state = true;
+ }
+
+ this.data.hooks.canplayall = {
+ fired: state
+ };
+ },
+ // declare special handling instructions
+ handler: function canplayall( event, callback ) {
+
+ if ( !this.data.hooks.canplayall.fired ) {
+ // trigger original user callback once
+ callback.call( this, event );
+
+ this.data.hooks.canplayall.fired = true;
+ }
+ }
+ }
+ }
+ };
+
+ // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
+ Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) {
+ Popcorn.p[ key ] = Popcorn.events.fn[ key ];
+ });
+
+ // Internal Only - Adds track events to the instance object
+ Popcorn.addTrackEvent = function( obj, track ) {
+
+ // Determine if this track has default options set for it
+ // If so, apply them to the track object
+ if ( track && track._natives && track._natives.type &&
+ ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
+
+ track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
+ }
+
+ if ( track._natives ) {
+ // Supports user defined track event id
+ track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
+
+ // Push track event ids into the history
+ obj.data.history.push( track._id );
+ }
+
+ track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
+ track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate );
+
+ // Store this definition in an array sorted by times
+ var byStart = obj.data.trackEvents.byStart,
+ byEnd = obj.data.trackEvents.byEnd,
+ idx;
+
+ for ( idx = byStart.length - 1; idx >= 0; idx-- ) {
+
+ if ( track.start >= byStart[ idx ].start ) {
+ byStart.splice( idx + 1, 0, track );
+ break;
+ }
+ }
+
+ for ( idx = byEnd.length - 1; idx >= 0; idx-- ) {
+
+ if ( track.end > byEnd[ idx ].end ) {
+ byEnd.splice( idx + 1, 0, track );
+ break;
+ }
+ }
+
+ this.timeUpdate( obj, null );
+
+ // Store references to user added trackevents in ref table
+ if ( track._id ) {
+ Popcorn.addTrackEvent.ref( obj, track );
+ }
+ };
+
+ // Internal Only - Adds track event references to the instance object's trackRefs hash table
+ Popcorn.addTrackEvent.ref = function( obj, track ) {
+ obj.data.trackRefs[ track._id ] = track;
+
+ return obj;
+ };
+
+ Popcorn.removeTrackEvent = function( obj, trackId ) {
+
+ var historyLen = obj.data.history.length,
+ indexWasAt = 0,
+ byStart = [],
+ byEnd = [],
+ animating = [],
+ history = [];
+
+ Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
+ // Preserve the original start/end trackEvents
+ if ( !o._id ) {
+ byStart.push( obj.data.trackEvents.byStart[i] );
+ byEnd.push( obj.data.trackEvents.byEnd[i] );
+ }
+
+ // Filter for user track events (vs system track events)
+ if ( o._id ) {
+
+ // Filter for the trackevent to remove
+ if ( o._id !== trackId ) {
+ byStart.push( obj.data.trackEvents.byStart[i] );
+ byEnd.push( obj.data.trackEvents.byEnd[i] );
+ }
+
+ // Capture the position of the track being removed.
+ if ( o._id === trackId ) {
+ indexWasAt = i;
+ o._natives._teardown && o._natives._teardown.call( obj, o );
+ }
+ }
+
+ });
+
+ if ( obj.data.trackEvents.animating.length ) {
+ Popcorn.forEach( obj.data.trackEvents.animating, function( o, i, context ) {
+ // Preserve the original start/end trackEvents
+ if ( !o._id ) {
+ animating.push( obj.data.trackEvents.animating[i] );
+ }
+
+ // Filter for user track events (vs system track events)
+ if ( o._id ) {
+ // Filter for the trackevent to remove
+ if ( o._id !== trackId ) {
+ animating.push( obj.data.trackEvents.animating[i] );
+ }
+ }
+ });
+ }
+
+ // Update
+ if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
+ obj.data.trackEvents.startIndex--;
+ }
+
+ if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
+ obj.data.trackEvents.endIndex--;
+ }
+
+ obj.data.trackEvents.byStart = byStart;
+ obj.data.trackEvents.byEnd = byEnd;
+ obj.data.trackEvents.animating = animating;
+
+ for ( var i = 0; i < historyLen; i++ ) {
+ if ( obj.data.history[ i ] !== trackId ) {
+ history.push( obj.data.history[ i ] );
+ }
+ }
+
+ // Update ordered history array
+ obj.data.history = history;
+
+ // Update track event references
+ Popcorn.removeTrackEvent.ref( obj, trackId );
+ };
+
+ // Internal Only - Removes track event references from instance object's trackRefs hash table
+ Popcorn.removeTrackEvent.ref = function( obj, trackId ) {
+ delete obj.data.trackRefs[ trackId ];
+
+ return obj;
+ };
+
+ // Return an array of track events bound to this instance object
+ Popcorn.getTrackEvents = function( obj ) {
+
+ var trackevents = [],
+ refs = obj.data.trackEvents.byStart,
+ length = refs.length,
+ idx = 0,
+ ref;
+
+ for ( ; idx < length; idx++ ) {
+ ref = refs[ idx ];
+ // Return only user attributed track event references
+ if ( ref._id ) {
+ trackevents.push( ref );
+ }
+ }
+
+ return trackevents;
+ };
+
+ // Internal Only - Returns an instance object's trackRefs hash table
+ Popcorn.getTrackEvents.ref = function( obj ) {
+ return obj.data.trackRefs;
+ };
+
+ // Return a single track event bound to this instance object
+ Popcorn.getTrackEvent = function( obj, trackId ) {
+ return obj.data.trackRefs[ trackId ];
+ };
+
+ // Internal Only - Returns an instance object's track reference by track id
+ Popcorn.getTrackEvent.ref = function( obj, trackId ) {
+ return obj.data.trackRefs[ trackId ];
+ };
+
+ Popcorn.getLastTrackEventId = function( obj ) {
+ return obj.data.history[ obj.data.history.length - 1 ];
+ };
+
+ Popcorn.timeUpdate = function( obj, event ) {
+
+ var currentTime = obj.media.currentTime,
+ previousTime = obj.data.trackEvents.previousUpdateTime,
+ tracks = obj.data.trackEvents,
+ animating = tracks.animating,
+ end = tracks.endIndex,
+ start = tracks.startIndex,
+ animIndex = 0,
+
+ registryByName = Popcorn.registryByName,
+
+ byEnd, byStart, byAnimate, natives, type;
+
+ // Playbar advancing
+ if ( previousTime <= currentTime ) {
+
+ while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
+
+ byEnd = tracks.byEnd[ end ];
+ natives = byEnd._natives;
+ type = natives && natives.type;
+
+ // If plugin does not exist on this instance, remove it
+ if ( !natives ||
+ ( !!registryByName[ type ] ||
+ !!obj[ type ] ) ) {
+
+ if ( byEnd._running === true ) {
+ byEnd._running = false;
+ natives.end.call( obj, event, byEnd );
+ }
+
+ end++;
+ } else {
+ // remove track event
+ Popcorn.removeTrackEvent( obj, byEnd._id );
+ return;
+ }
+ }
+
+ while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
+
+ byStart = tracks.byStart[ start ];
+ natives = byStart._natives;
+ type = natives && natives.type;
+
+ // If plugin does not exist on this instance, remove it
+ if ( !natives ||
+ ( !!registryByName[ type ] ||
+ !!obj[ type ] ) ) {
+
+ if ( byStart.end > currentTime &&
+ byStart._running === false &&
+ obj.data.disabled.indexOf( type ) === -1 ) {
+
+ byStart._running = true;
+ natives.start.call( obj, event, byStart );
+
+ // If the `frameAnimation` option is used,
+ // push the current byStart object into the `animating` cue
+ if ( obj.options.frameAnimation &&
+ ( byStart && byStart._running && byStart._natives.frame ) ) {
+
+ animating.push( byStart );
+ }
+ }
+ start++;
+ } else {
+ // remove track event
+ Popcorn.removeTrackEvent( obj, byStart._id );
+ return;
+ }
+ }
+
+ // If the `frameAnimation` option is used, iterate the animating track
+ // and execute the `frame` callback
+ if ( obj.options.frameAnimation ) {
+ while ( animIndex < animating.length ) {
+
+ byAnimate = animating[ animIndex ];
+
+ if ( !byAnimate._running ) {
+ animating.splice( animIndex, 1 );
+ } else {
+ byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
+ animIndex++;
+ }
+ }
+ }
+
+ // Playbar receding
+ } else if ( previousTime > currentTime ) {
+
+ while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
+
+ byStart = tracks.byStart[ start ];
+ natives = byStart._natives;
+ type = natives && natives.type;
+
+ // if plugin does not exist on this instance, remove it
+ if ( !natives ||
+ ( !!registryByName[ type ] ||
+ !!obj[ type ] ) ) {
+
+ if ( byStart._running === true ) {
+ byStart._running = false;
+ natives.end.call( obj, event, byStart );
+ }
+ start--;
+ } else {
+ // remove track event
+ Popcorn.removeTrackEvent( obj, byStart._id );
+ return;
+ }
+ }
+
+ while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
+
+ byEnd = tracks.byEnd[ end ];
+ natives = byEnd._natives;
+ type = natives && natives.type;
+
+ // if plugin does not exist on this instance, remove it
+ if ( !natives ||
+ ( !!registryByName[ type ] ||
+ !!obj[ type ] ) ) {
+
+ if ( byEnd.start <= currentTime &&
+ byEnd._running === false &&
+ obj.data.disabled.indexOf( type ) === -1 ) {
+
+ byEnd._running = true;
+ natives.start.call( obj, event, byEnd );
+
+ // If the `frameAnimation` option is used,
+ // push the current byEnd object into the `animating` cue
+ if ( obj.options.frameAnimation &&
+ ( byEnd && byEnd._running && byEnd._natives.frame ) ) {
+
+ animating.push( byEnd );
+ }
+ }
+ end--;
+ } else {
+ // remove track event
+ Popcorn.removeTrackEvent( obj, byEnd._id );
+ return;
+ }
+ }
+
+ // If the `frameAnimation` option is used, iterate the animating track
+ // and execute the `frame` callback
+ if ( obj.options.frameAnimation ) {
+ while ( animIndex < animating.length ) {
+
+ byAnimate = animating[ animIndex ];
+
+ if ( !byAnimate._running ) {
+ animating.splice( animIndex, 1 );
+ } else {
+ byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
+ animIndex++;
+ }
+ }
+ }
+ // time bar is not moving ( video is paused )
+ }
+
+ tracks.endIndex = end;
+ tracks.startIndex = start;
+ tracks.previousUpdateTime = currentTime;
+ };
+
+ // Map and Extend TrackEvent functions to all Popcorn instances
+ Popcorn.extend( Popcorn.p, {
+
+ getTrackEvents: function() {
+ return Popcorn.getTrackEvents.call( null, this );
+ },
+
+ getTrackEvent: function( id ) {
+ return Popcorn.getTrackEvent.call( null, this, id );
+ },
+
+ getLastTrackEventId: function() {
+ return Popcorn.getLastTrackEventId.call( null, this );
+ },
+
+ removeTrackEvent: function( id ) {
+
+ Popcorn.removeTrackEvent.call( null, this, id );
+ return this;
+ },
+
+ removePlugin: function( name ) {
+ Popcorn.removePlugin.call( null, this, name );
+ return this;
+ },
+
+ timeUpdate: function( event ) {
+ Popcorn.timeUpdate.call( null, this, event );
+ return this;
+ },
+
+ destroy: function() {
+ Popcorn.destroy.call( null, this );
+ return this;
+ }
+ });
+
+ // Plugin manifests
+ Popcorn.manifest = {};
+ // Plugins are registered
+ Popcorn.registry = [];
+ Popcorn.registryByName = {};
+ // An interface for extending Popcorn
+ // with plugin functionality
+ Popcorn.plugin = function( name, definition, manifest ) {
+
+ if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
+ Popcorn.error( "'" + name + "' is a protected function name" );
+ return;
+ }
+
+ // Provides some sugar, but ultimately extends
+ // the definition into Popcorn.p
+ var reserved = [ "start", "end" ],
+ plugin = {},
+ setup,
+ isfn = typeof definition === "function",
+ methods = [ "_setup", "_teardown", "start", "end", "frame" ];
+
+ // 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 );
+ };
+ };
+
+ // If `manifest` arg is undefined, check for manifest within the `definition` object
+ // If no `definition.manifest`, an empty object is a sufficient fallback
+ Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
+
+ // apply safe, and empty default functions
+ methods.forEach(function( method ) {
+ definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
+ });
+
+ var pluginFn = function( setup, options ) {
+
+ if ( !options ) {
+ return this;
+ }
+
+ // Storing the plugin natives
+ var natives = options._natives = {},
+ compose = "",
+ defaults, originalOpts, manifestOpts, mergedSetupOpts;
+
+ Popcorn.extend( natives, setup );
+
+ options._natives.type = name;
+ options._running = false;
+
+ natives.start = natives.start || natives[ "in" ];
+ natives.end = natives.end || natives[ "out" ];
+
+ // Check for previously set default options
+ defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ];
+
+ // default to an empty string if no effect exists
+ // split string into an array of effects
+ options.compose = options.compose && options.compose.split( " " ) || [];
+ options.effect = options.effect && options.effect.split( " " ) || [];
+
+ // join the two arrays together
+ options.compose = options.compose.concat( options.effect );
+
+ options.compose.forEach(function( composeOption ) {
+
+ // if the requested compose is garbage, throw it away
+ compose = Popcorn.compositions[ composeOption ] || {};
+
+ // extends previous functions with compose function
+ methods.forEach(function( method ) {
+ natives[ method ] = combineFn( natives[ method ], compose[ method ] );
+ });
+ });
+
+ // Ensure a manifest object, an empty object is a sufficient fallback
+ options._natives.manifest = manifest;
+
+ // Checks for expected properties
+ if ( !( "start" in options ) ) {
+ options.start = options[ "in" ] || 0;
+ }
+
+ if ( !( "end" in options ) ) {
+ options.end = options[ "out" ] || this.duration() || Number.MAX_VALUE;
+ }
+
+ // Merge with defaults if they exist, make sure per call is prioritized
+ mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) :
+ options;
+
+ // Resolves 239, 241, 242
+ if ( !mergedSetupOpts.target ) {
+
+ // Sometimes the manifest may be missing entirely
+ // or it has an options object that doesn't have a `target` property
+ manifestOpts = "options" in manifest && manifest.options;
+
+ mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
+ }
+
+ // Trigger _setup method if exists
+ options._natives._setup && options._natives._setup.call( this, mergedSetupOpts );
+
+ // Create new track event for this instance
+ Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) );
+
+ // Future support for plugin event definitions
+ // for all of the native events
+ Popcorn.forEach( setup, function( callback, type ) {
+
+ if ( type !== "type" ) {
+
+ if ( reserved.indexOf( type ) === -1 ) {
+
+ this.listen( type, callback );
+ }
+ }
+
+ }, this );
+
+ return this;
+ };
+
+ // Assign new named definition
+ plugin[ name ] = function( options ) {
+ return pluginFn.call( this, isfn ? definition.call( this, options ) : definition,
+ options );
+ };
+
+ // Extend Popcorn.p with new named definition
+ Popcorn.extend( Popcorn.p, plugin );
+
+ // Push into the registry
+ var entry = {
+ fn: plugin[ name ],
+ definition: definition,
+ base: definition,
+ parents: [],
+ name: name
+ };
+ Popcorn.registry.push(
+ Popcorn.extend( plugin, entry, {
+ type: name
+ })
+ );
+ Popcorn.registryByName[ name ] = entry;
+
+ return plugin;
+ };
+
+ // Storage for plugin function errors
+ Popcorn.plugin.errors = [];
+
+ // Returns wrapped plugin function
+ function safeTry( fn, pluginName ) {
+ return function() {
+
+ // When Popcorn.plugin.debug is true, do not suppress errors
+ if ( Popcorn.plugin.debug ) {
+ return fn.apply( this, arguments );
+ }
+
+ try {
+ return fn.apply( this, arguments );
+ } catch ( ex ) {
+
+ // Push plugin function errors into logging queue
+ Popcorn.plugin.errors.push({
+ plugin: pluginName,
+ thrown: ex,
+ source: fn.toString()
+ });
+
+ // Trigger an error that the instance can listen for
+ // and react to
+ this.trigger( "error", Popcorn.plugin.errors );
+ }
+ };
+ }
+
+ // Debug-mode flag for plugin development
+ Popcorn.plugin.debug = false;
+
+ // removePlugin( type ) removes all tracks of that from all instances of popcorn
+ // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
+ Popcorn.removePlugin = function( obj, name ) {
+
+ // Check if we are removing plugin from an instance or from all of Popcorn
+ if ( !name ) {
+
+ // Fix the order
+ name = obj;
+ obj = Popcorn.p;
+
+ if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
+ Popcorn.error( "'" + name + "' is a protected function name" );
+ return;
+ }
+
+ var registryLen = Popcorn.registry.length,
+ registryIdx;
+
+ // remove plugin reference from registry
+ for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
+ if ( Popcorn.registry[ registryIdx ].name === name ) {
+ Popcorn.registry.splice( registryIdx, 1 );
+ delete Popcorn.registryByName[ name ];
+
+ // delete the plugin
+ delete obj[ name ];
+
+ // plugin found and removed, stop checking, we are done
+ return;
+ }
+ }
+
+ }
+
+ var byStart = obj.data.trackEvents.byStart,
+ byEnd = obj.data.trackEvents.byEnd,
+ animating = obj.data.trackEvents.animating,
+ idx, sl;
+
+ // remove all trackEvents
+ for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
+
+ if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) &&
+ ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) {
+
+ byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
+
+ byStart.splice( idx, 1 );
+ byEnd.splice( idx, 1 );
+
+ // update for loop if something removed, but keep checking
+ idx--; sl--;
+ if ( obj.data.trackEvents.startIndex <= idx ) {
+ obj.data.trackEvents.startIndex--;
+ obj.data.trackEvents.endIndex--;
+ }
+ }
+ }
+
+ //remove all animating events
+ for ( idx = 0, sl = animating.length; idx < sl; idx++ ) {
+
+ if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) {
+
+ animating.splice( idx, 1 );
+
+ // update for loop if something removed, but keep checking
+ idx--; sl--;
+ }
+ }
+
+ };
+
+ Popcorn.compositions = {};
+
+ // Plugin inheritance
+ Popcorn.compose = function( name, definition, manifest ) {
+
+ // If `manifest` arg is undefined, check for manifest within the `definition` object
+ // If no `definition.manifest`, an empty object is a sufficient fallback
+ Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
+
+ // register the effect by name
+ Popcorn.compositions[ name ] = definition;
+ };
+
+ Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
+
+ // stores parsers keyed on filetype
+ Popcorn.parsers = {};
+
+ // An interface for extending Popcorn
+ // with parser functionality
+ Popcorn.parser = function( name, type, definition ) {
+
+ if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
+ Popcorn.error( "'" + name + "' is a protected function name" );
+ return;
+ }
+
+ // fixes parameters for overloaded function call
+ if ( typeof type === "function" && !definition ) {
+ definition = type;
+ type = "";
+ }
+
+ if ( typeof definition !== "function" || typeof type !== "string" ) {
+ return;
+ }
+
+ // Provides some sugar, but ultimately extends
+ // the definition into Popcorn.p
+
+ var natives = Popcorn.events.all,
+ parseFn,
+ parser = {};
+
+ parseFn = function( filename, callback ) {
+
+ if ( !filename ) {
+ return this;
+ }
+
+ var that = this;
+
+ Popcorn.xhr({
+ url: filename,
+ dataType: type,
+ success: function( data ) {
+
+ var tracksObject = definition( data ),
+ tracksData,
+ tracksDataLen,
+ tracksDef,
+ idx = 0;
+
+ tracksData = tracksObject.data || [];
+ tracksDataLen = tracksData.length;
+ tracksDef = null;
+
+ // If no tracks to process, return immediately
+ if ( !tracksDataLen ) {
+ return;
+ }
+
+ // Create tracks out of parsed object
+ for ( ; idx < tracksDataLen; idx++ ) {
+
+ tracksDef = tracksData[ idx ];
+
+ for ( var key in tracksDef ) {
+
+ if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
+
+ that[ key ]( tracksDef[ key ] );
+ }
+ }
+ }
+ if ( callback ) {
+ callback();
+ }
+ }
+ });
+
+ return this;
+ };
+
+ // Assign new named definition
+ parser[ name ] = parseFn;
+
+ // Extend Popcorn.p with new named definition
+ Popcorn.extend( Popcorn.p, parser );
+
+ // keys the function name by filetype extension
+ //Popcorn.parsers[ name ] = true;
+
+ return parser;
+ };
+
+ Popcorn.player = function( name, player ) {
+
+ player = player || {};
+
+ var playerFn = function( target, src, options ) {
+
+ options = options || {};
+
+ // List of events
+ var date = new Date() / 1000,
+ baselineTime = date,
+ currentTime = 0,
+ volume = 1,
+ muted = false,
+ events = {},
+
+ // The container div of the resource
+ container = document.getElementById( rIdExp.exec( target ) && rIdExp.exec( target )[ 2 ] ) ||
+ document.getElementById( target ) ||
+ target,
+ basePlayer = {},
+ timeout,
+ popcorn;
+
+ // copies a div into the media object
+ for( var val in container ) {
+
+ if ( typeof container[ val ] === "object" ) {
+
+ basePlayer[ val ] = container[ val ];
+ } else if ( typeof container[ val ] === "function" ) {
+
+ basePlayer[ val ] = (function( value ) {
+
+ 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
+ });
+
+ // Adds an event listener to the object
+ basePlayer.addEventListener = function( evtName, fn ) {
+
+ if ( !events[ evtName ] ) {
+
+ events[ evtName ] = [];
+ }
+
+ events[ evtName ].push( fn );
+ 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 );
+ }
+ }
+
+ Popcorn.forEach( events[ eventName ], function( val ) {
+
+ val.call( self, evt, self );
+ });
+ };
+
+ // Attempt to get src from playerFn parameter
+ basePlayer.src = src || "";
+ basePlayer.readyState = 0;
+ basePlayer.duration = 0;
+ basePlayer.paused = true;
+ basePlayer.ended = 0;
+
+ 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( "load" );
+ basePlayer.dispatchEvent( "loadeddata" );
+ }
+
+ // when a custom player is loaded, load basePlayer state into custom player
+ basePlayer.addEventListener( "load", function() {
+
+ // if a player is not ready before currentTime is called, this will set it after it is ready
+ basePlayer.currentTime = currentTime;
+
+ // same as above with volume and muted
+ basePlayer.volume = volume;
+ basePlayer.muted = muted;
+ });
+
+ basePlayer.addEventListener( "loadeddata", function() {
+
+ // if play was called before player ready, start playing video
+ !basePlayer.paused && basePlayer.play();
+ });
+
+ popcorn = new Popcorn.p.init( basePlayer, options );
+
+ return popcorn;
+ };
+
+ Popcorn[ name ] = Popcorn[ name ] || playerFn;
+ };
+
+ Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
+
+ object.__defineGetter__( description, options.get || Popcorn.nop );
+ object.__defineSetter__( description, options.set || Popcorn.nop );
+ };
+
+ // Cache references to reused RegExps
+ var rparams = /\?/,
+ // XHR Setup object
+ setup = {
+ url: "",
+ data: "",
+ dataType: "",
+ success: Popcorn.nop,
+ type: "GET",
+ async: true,
+ xhr: function() {
+ return new global.XMLHttpRequest();
+ }
+ };
+
+ Popcorn.xhr = function( options ) {
+
+ options.dataType = options.dataType && options.dataType.toLowerCase() || null;
+
+ if ( options.dataType &&
+ ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
+
+ Popcorn.xhr.getJSONP(
+ options.url,
+ options.success,
+ options.dataType === "script"
+ );
+ return;
+ }
+
+ var settings = Popcorn.extend( {}, setup, options );
+
+ // Create new XMLHttpRequest object
+ settings.ajax = settings.xhr();
+
+ if ( settings.ajax ) {
+
+ if ( settings.type === "GET" && settings.data ) {
+
+ // append query string
+ settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
+
+ // Garbage collect and reset settings.data
+ settings.data = null;
+ }
+
+
+ settings.ajax.open( settings.type, settings.url, settings.async );
+ settings.ajax.send( settings.data || null );
+
+ return Popcorn.xhr.httpData( settings );
+ }
+ };
+
+
+ Popcorn.xhr.httpData = function( settings ) {
+
+ var data, json = null;
+
+ settings.ajax.onreadystatechange = function() {
+
+ if ( settings.ajax.readyState === 4 ) {
+
+ try {
+ json = JSON.parse( settings.ajax.responseText );
+ } catch( e ) {
+ //suppress
+ }
+
+ data = {
+ xml: settings.ajax.responseXML,
+ text: settings.ajax.responseText,
+ json: json
+ };
+
+ // If a dataType was specified, return that type of data
+ if ( settings.dataType ) {
+ data = data[ settings.dataType ];
+ }
+
+
+ settings.success.call( settings.ajax, data );
+
+ }
+ };
+ return data;
+ };
+
+ Popcorn.xhr.getJSONP = function( url, success, isScript ) {
+
+ var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
+ script = document.createElement( "script" ),
+ paramStr = url.split( "?" )[ 1 ],
+ isFired = false,
+ params = [],
+ callback, parts, callparam;
+
+ if ( paramStr && !isScript ) {
+ params = paramStr.split( "&" );
+ }
+
+ if ( params.length ) {
+ parts = params[ params.length - 1 ].split( "=" );
+ }
+
+ callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ] ) : "jsonp";
+
+ if ( !paramStr && !isScript ) {
+ url += "?callback=" + callback;
+ }
+
+ if ( callback && !isScript ) {
+
+ // If a callback name already exists
+ if ( !!window[ callback ] ) {
+ // Create a new unique callback name
+ callback = Popcorn.guid( callback );
+ }
+
+ // Define the JSONP success callback globally
+ window[ callback ] = function( data ) {
+ // Fire success callbacks
+ success && success( data );
+ isFired = true;
+ };
+
+ // Replace callback param and callback name
+ url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback );
+ }
+
+ script.onload = function() {
+
+ // Handling remote script loading callbacks
+ if ( isScript ) {
+ // getScript
+ success && success();
+ }
+
+ // Executing for JSONP requests
+ if ( isFired ) {
+ // Garbage collect the callback
+ delete window[ callback ];
+ }
+ // Garbage collect the script resource
+ head.removeChild( script );
+ };
+
+ script.src = url;
+
+ head.insertBefore( script, head.firstChild );
+
+ return;
+ };
+
+ Popcorn.getJSONP = Popcorn.xhr.getJSONP;
+
+ Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
+
+ return Popcorn.xhr.getJSONP( url, success, true );
+ };
+
+ Popcorn.util = {
+ // Simple function to parse a timestamp into seconds
+ // Acceptable formats are:
+ // HH:MM:SS.MMM
+ // HH:MM:SS;FF
+ // Hours and minutes are optional. They default to 0
+ toSeconds: function( timeStr, framerate ) {
+ // Hours and minutes are optional
+ // Seconds must be specified
+ // Seconds can be followed by milliseconds OR by the frame information
+ var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
+ errorMessage = "Invalid time format",
+ digitPairs, lastIndex, lastPair, firstPair,
+ frameInfo, frameTime;
+
+ if ( typeof timeStr === "number" ) {
+ return timeStr;
+ }
+
+ if ( typeof timeStr === "string" &&
+ !validTimeFormat.test( timeStr ) ) {
+ Popcorn.error( errorMessage );
+ }
+
+ digitPairs = timeStr.split( ":" );
+ lastIndex = digitPairs.length - 1;
+ lastPair = digitPairs[ lastIndex ];
+
+ // Fix last element:
+ if ( lastPair.indexOf( ";" ) > -1 ) {
+
+ frameInfo = lastPair.split( ";" );
+ frameTime = 0;
+
+ if ( framerate && ( typeof framerate === "number" ) ) {
+ frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
+ }
+
+ digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime;
+ }
+
+ firstPair = digitPairs[ 0 ];
+
+ return {
+
+ 1: parseFloat( firstPair, 10 ),
+
+ 2: ( parseInt( firstPair, 10 ) * 60 ) +
+ parseFloat( digitPairs[ 1 ], 10 ),
+
+ 3: ( parseInt( firstPair, 10 ) * 3600 ) +
+ ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) +
+ parseFloat( digitPairs[ 2 ], 10 )
+
+ }[ digitPairs.length || 1 ];
+ }
+ };
+
+
+ // Initialize locale data
+ // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes
+ function initLocale( arg ) {
+
+ var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ),
+ parts = locale.split( "-" );
+
+ // Setup locale data table
+ return {
+ iso6391: locale,
+ language: parts[ 0 ] || "",
+ region: parts[ 1 ] || ""
+ };
+ }
+
+ // Declare locale data table
+ var localeData = initLocale( global.navigator.userLanguage || global.navigator.language );
+
+ Popcorn.locale = {
+
+ // Popcorn.locale.get()
+ // returns reference to privately
+ // defined localeData
+ get: function() {
+ return localeData;
+ },
+
+ // Popcorn.locale.set( string|object );
+ set: function( arg ) {
+
+ localeData = initLocale( arg );
+
+ Popcorn.locale.broadcast();
+
+ return localeData;
+ },
+
+ // Popcorn.locale.broadcast( type )
+ // Sends events to all popcorn media instances that are
+ // listening for locale events
+ broadcast: function( type ) {
+
+ var instances = Popcorn.instances,
+ length = instances.length,
+ idx = 0,
+ instance;
+
+ type = type || "locale:changed";
+
+ // Iterate all current instances
+ for ( ; idx < length; idx++ ) {
+ instance = instances[ idx ];
+
+ // For those instances with locale event listeners,
+ // trigger a locale change event
+ if ( type in instance.data.events ) {
+ instance.trigger( type );
+ }
+ }
+ }
+ };
+
+ // alias for exec function
+ Popcorn.p.cue = Popcorn.p.exec;
+
+ function getItems() {
+
+ var item,
+ list = [];
+
+ if ( Object.keys ) {
+ list = Object.keys( Popcorn.p );
+ } else {
+
+ for ( item in Popcorn.p ) {
+ if ( hasOwn.call( Popcorn.p, item ) ) {
+ list.push( item );
+ }
+ }
+ }
+
+ return list.join( "," ).toLowerCase().split( ",");
+ }
+
+ // Protected API methods
+ Popcorn.protect = {
+ natives: getItems()
+ };
+
+ // Exposes Popcorn to global context
+ global.Popcorn = Popcorn;
+
+})(window, window.document);