Adding required popcorn files. Also changed the build file to include those popcorn-port
authorhamidouk
Mon, 03 Oct 2011 16:08:36 +0200
branchpopcorn-port
changeset 51 1444edeae73f
parent 50 b5849024c5c5
child 52 2ec3deb0a0d5
Adding required popcorn files. Also changed the build file to include those files.
sbin/build/client.xml
src/js/popcorn.js
src/js/popcorn.jwplayer.js
src/js/popcorn.youtube.js
--- a/sbin/build/client.xml	Tue Sep 20 13:11:53 2011 +0200
+++ b/sbin/build/client.xml	Mon Oct 03 16:08:36 2011 +0200
@@ -69,6 +69,8 @@
 	    	
 	<target name="concatenate" description="Build the developer release file" depends="make_templates">
 	    <concat encoding="UTF-8" outputencoding="UTF-8" destfile="../../build/LdtPlayer-release.js">
+			<filelist dir="../../src/js/" files="header.js" />
+	        <filelist dir="../../src/js" files="popcorn.js popcorn.youtube.js popcorn.jwplayer.js" />
 	        <filelist dir="../../src/js" files="LdtPlayer.js" />
 	    	<filelist dir="../../build" files="compiled_templates.js" />
 	    	<filelist dir="../../src/js" files="mustache.js utils.js data.js site.js ui.js" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/popcorn.js	Mon Oct 03 16:08:36 2011 +0200
@@ -0,0 +1,1983 @@
+(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() {
+      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,
+          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
+      });
+
+      // 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;
+
+      // basePlayer has no concept of sound
+      basePlayer.volume = 1;
+      basePlayer.muted = 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( 'load' );
+      }
+
+      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;
+
+  //  Protected API methods
+  Popcorn.protect = {
+    natives: Object.keys( Popcorn.p ).join( "," ).toLowerCase().split( "," )
+  };
+
+  //  Exposes Popcorn to global context
+  global.Popcorn = Popcorn;
+
+})(window, window.document);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/popcorn.jwplayer.js	Mon Oct 03 16:08:36 2011 +0200
@@ -0,0 +1,118 @@
+var jwplayerObjects = {};
+
+Popcorn.player( "jwplayer", {
+  _setup: function( options ) {
+
+    var media = this,
+        player = {},
+        container = document.createElement( "div" ),
+        currentTime = 0,
+        seekTime = 0,
+        seeking = false,
+        dataLoaded = false;
+    container.id = media.id + Popcorn.guid();
+
+    media.appendChild( container );
+
+  var initApi = function () {
+    jwplayer( container.id ).onTime(function() {
+        currentTime = jwplayer(container.id).getPosition();
+        media.dispatchEvent( "timeupdate" );
+       // timeout = setTimeout( timeupdate, 10 );
+    });
+    
+    media.play = function() {
+      media.paused = false;
+      media.dispatchEvent( "play" );
+      if ( dataLoaded ) {
+        media.dispatchEvent( "loadeddata" );
+        dataLoaded = false;
+      }
+
+      media.dispatchEvent( "playing" );
+      jwplayer( container.id ).play();
+    };
+    
+    media.pause = function() {
+
+      if ( !media.paused ) {
+        media.paused = true;
+        media.dispatchEvent( "pause" );
+        jwplayer( container.id ).pause();
+      }
+    };
+
+    Popcorn.player.defineProperty( media, "currentTime", {
+          set: function( val ) {
+            // make sure val is a number
+            currentTime = seekTime = +val;
+            seeking = true;
+            media.dispatchEvent( "seeked" );
+            media.dispatchEvent( "timeupdate" );
+            jwplayer( container.id ).seek( currentTime );
+            return currentTime;
+          },
+          get: function() {
+            return jwplayer( container.id ).getPosition();            
+          }
+        });
+ 
+    Popcorn.player.defineProperty( media, "muted", {   
+        set: function( val ) {
+          if ( jwplayer( container.id ).getMute() !== val ) {
+            if ( val ) {
+              jwplayer( container.id ).setMute(true);
+            } else {
+              jwplayer( container.id ).setMute(false);
+            }
+
+            media.dispatchEvent( "volumechange" );
+          }
+          
+          return jwplayer( container.id ).getMute();
+        },
+        get: function() {
+          return jwplayer( container.id ).getMute();
+        }
+    });
+  
+    Popcorn.player.defineProperty( media, "volume", {
+    
+      set: function( val ) {
+
+        if ( jwplayer( container.id ).getVolume() !== val *100 ) {
+          jwplayer( container.id ).setVolume( val * 100);
+          media.dispatchEvent( "volumechange" );
+        }
+        
+        return (jwplayer( container.id ).getVolume()) / 100;
+      },
+      
+      get: function() {
+        return jwplayer( container.id ).getVolume() / 100;
+      }
+    });
+
+    media.readyState = 4;
+    media.dispatchEvent( 'load' );
+    dataLoaded = true;
+
+    media.duration = options.duration;
+    media.dispatchEvent( 'durationchange' );
+
+    media.paused && media.dispatchEvent( 'loadeddata' );
+
+    };
+
+    jwplayer( container.id ).setup({
+      file: options.file,
+      height: 270,
+      width: 480,
+      flashplayer: options.flashplayer,
+      events: {
+        onReady: initApi
+      }});
+
+  }
+});
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/popcorn.youtube.js	Mon Oct 03 16:08:36 2011 +0200
@@ -0,0 +1,233 @@
+// A global callback for youtube... that makes me angry
+var onYouTubePlayerReady = function( containerId ) {
+
+  onYouTubePlayerReady[ containerId ] && onYouTubePlayerReady[ containerId ]();
+};
+onYouTubePlayerReady.stateChangeEventHandler = {};
+
+Popcorn.player( "youtube", {
+  _setup: function( options ) {
+
+    var media = this,
+        youtubeObject,
+        container = document.createElement( "div" ),
+        currentTime = 0,
+        seekTime = 0,
+        seeking = false,
+        dataLoaded = false,
+
+        // state code for volume changed polling
+        volumeChanged = false,
+        lastMuted = false,
+        lastVolume = 0;
+
+    container.id = media.id + Popcorn.guid();
+
+    media.appendChild( container );
+
+    var youtubeInit = function() {
+
+      var flashvars,
+          params,
+          attributes,
+          src;
+
+      // expose a callback to this scope, that is called from the global callback youtube calls
+      onYouTubePlayerReady[ container.id ] = function() {
+
+        youtubeObject = document.getElementById( container.id );
+
+        // more youtube callback nonsense
+        onYouTubePlayerReady.stateChangeEventHandler[ container.id ] = function( state ) {
+
+          // playing is state 1
+          // paused is state 2
+          if ( state === 1 ) {
+
+            media.paused && media.play();
+          // youtube fires paused events while seeking
+          // this is the only way to get seeking events
+          } else if ( state === 2 ) {
+
+            // silly logic forced on me by the youtube API
+            // calling youtube.seekTo triggers multiple events
+            // with the second events getCurrentTime being the old time
+            if ( seeking && seekTime === currentTime && seekTime !== youtubeObject.getCurrentTime() ) {
+
+              seeking = false;
+              youtubeObject.seekTo( currentTime );
+              return;
+            }
+
+            currentTime = youtubeObject.getCurrentTime();
+            media.dispatchEvent( "timeupdate" );
+            !media.paused && media.pause();
+          }
+        };
+
+        // youtube requires callbacks to be a string to a function path from the global scope
+        youtubeObject.addEventListener( "onStateChange", "onYouTubePlayerReady.stateChangeEventHandler." + container.id );
+
+        var timeupdate = function() {
+
+          if ( !media.paused ) {
+
+            currentTime = youtubeObject.getCurrentTime();
+            media.dispatchEvent( "timeupdate" );
+            setTimeout( timeupdate, 10 );
+          }
+        };
+
+        var volumeupdate = function() {
+
+          if ( lastMuted !== youtubeObject.isMuted() ) {
+
+            lastMuted = youtubeObject.isMuted();
+            media.dispatchEvent( "volumechange" );
+          }
+
+          if ( lastVolume !== youtubeObject.getVolume() ) {
+
+            lastVolume = youtubeObject.getVolume();
+            media.dispatchEvent( "volumechange" );
+          }
+
+          setTimeout( volumeupdate, 250 );
+        };
+
+        media.play = function() {
+
+          media.paused = false;
+          media.dispatchEvent( "play" );
+
+          if ( dataLoaded ) {
+
+            media.dispatchEvent( "loadeddata" );
+            dataLoaded = false;
+          }
+
+          media.dispatchEvent( "playing" );
+          timeupdate();
+          youtubeObject.playVideo();
+        };
+
+        media.pause = function() {
+
+          if ( !media.paused ) {
+
+            media.paused = true;
+            media.dispatchEvent( "pause" );
+            youtubeObject.pauseVideo();
+          }
+        };
+
+        Popcorn.player.defineProperty( media, "currentTime", {
+          set: function( val ) {
+
+            // make sure val is a number
+            currentTime = seekTime = +val;
+            seeking = true;
+            media.dispatchEvent( "seeked" );
+            media.dispatchEvent( "timeupdate" );
+            youtubeObject.seekTo( currentTime );
+            return currentTime;
+          },
+          get: function() {
+
+            return currentTime;
+          }
+        });
+
+        Popcorn.player.defineProperty( media, "muted", {
+          set: function( val ) {
+
+            if ( youtubeObject.isMuted() !== val ) {
+
+              if ( val ) {
+
+                youtubeObject.mute();
+              } else {
+
+                youtubeObject.unMute();
+              }
+
+              lastMuted = youtubeObject.isMuted();
+              media.dispatchEvent( "volumechange" );
+            }
+
+            return youtubeObject.isMuted();
+          },
+          get: function() {
+
+            return youtubeObject.isMuted();
+          }
+        });
+
+        Popcorn.player.defineProperty( media, "volume", {
+          set: function( val ) {
+
+            if ( youtubeObject.getVolume() !== val ) {
+
+              youtubeObject.setVolume( val );
+              lastVolume = youtubeObject.getVolume();
+              media.dispatchEvent( "volumechange" );
+            }
+
+            return youtubeObject.getVolume();
+          },
+          get: function() {
+
+            return youtubeObject.getVolume();
+          }
+        });
+
+        media.readyState = 4;
+        media.dispatchEvent( "load" );
+        dataLoaded = true;
+        media.duration = youtubeObject.getDuration();
+        media.dispatchEvent( "durationchange" );
+        volumeupdate();
+
+        if ( !media.paused ) {
+
+          media.play();
+        }
+
+        media.paused && media.dispatchEvent( "loadeddata" );
+      };
+
+      options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1;
+      options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1;
+
+      flashvars = {
+        playerapiid: container.id,
+        controls: options.controls,
+        iv_load_policy: options.annotations
+      };
+
+      params = {
+        wmode: "transparent",
+        allowScriptAccess: "always"
+      };
+
+      attributes = {
+        id: container.id
+      };
+
+      src = /^.*[\/=](.{11})/.exec( media.src )[ 1 ];
+
+      swfobject.embedSWF( "http://www.youtube.com/e/" + src + "?enablejsapi=1&playerapiid=" + container.id + "&version=3",
+                          container.id, media.offsetWidth, media.offsetHeight, "8", null,
+                          flashvars, params, attributes );
+    };
+
+    if ( !window.swfobject ) {
+
+      Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", youtubeInit );
+    } else {
+
+      youtubeInit();
+    }
+  }
+});
+