test/integration/allocine_dossier_independant/js/libs/popcorn.js
branchnew-model
changeset 882 61c384dda19e
parent 881 f11b234497f7
child 883 d35ad8111c5e
equal deleted inserted replaced
881:f11b234497f7 882:61c384dda19e
     1 (function(global, document) {
       
     2 
       
     3   // Popcorn.js does not support archaic browsers
       
     4   if ( !document.addEventListener ) {
       
     5     global.Popcorn = {
       
     6       isSupported: false
       
     7     };
       
     8 
       
     9     var methods = ( "forEach extend effects error guid sizeOf isArray nop position disable enable destroy " +
       
    10           "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
       
    11           "timeUpdate plugin removePlugin compose effect parser xhr getJSONP getScript" ).split(/\s+/);
       
    12 
       
    13     while ( methods.length ) {
       
    14       global.Popcorn[ methods.shift() ] = function() {};
       
    15     }
       
    16     return;
       
    17   }
       
    18 
       
    19   var
       
    20 
       
    21   AP = Array.prototype,
       
    22   OP = Object.prototype,
       
    23 
       
    24   forEach = AP.forEach,
       
    25   slice = AP.slice,
       
    26   hasOwn = OP.hasOwnProperty,
       
    27   toString = OP.toString,
       
    28 
       
    29   // Copy global Popcorn (may not exist)
       
    30   _Popcorn = global.Popcorn,
       
    31 
       
    32   //  ID string matching
       
    33   rIdExp  = /^(#([\w\-\_\.]+))$/,
       
    34 
       
    35   //  Ready fn cache
       
    36   readyStack = [],
       
    37   readyBound = false,
       
    38   readyFired = false,
       
    39 
       
    40   //  Non-public internal data object
       
    41   internal = {
       
    42     events: {
       
    43       hash: {},
       
    44       apis: {}
       
    45     }
       
    46   },
       
    47 
       
    48   //  Non-public `requestAnimFrame`
       
    49   //  http://paulirish.com/2011/requestanimationframe-for-smart-animating/
       
    50   requestAnimFrame = (function(){
       
    51     return global.requestAnimationFrame ||
       
    52       global.webkitRequestAnimationFrame ||
       
    53       global.mozRequestAnimationFrame ||
       
    54       global.oRequestAnimationFrame ||
       
    55       global.msRequestAnimationFrame ||
       
    56       function( callback, element ) {
       
    57         global.setTimeout( callback, 16 );
       
    58       };
       
    59   }()),
       
    60 
       
    61   refresh = function( obj ) {
       
    62     var currentTime = obj.media.currentTime,
       
    63       animation = obj.options.frameAnimation,
       
    64       disabled = obj.data.disabled,
       
    65       tracks = obj.data.trackEvents,
       
    66       animating = tracks.animating,
       
    67       start = tracks.startIndex,
       
    68       registryByName = Popcorn.registryByName,
       
    69       animIndex = 0,
       
    70       byStart, natives, type;
       
    71 
       
    72     start = Math.min( start + 1, tracks.byStart.length - 2 );
       
    73 
       
    74     while ( start > 0 && tracks.byStart[ start ] ) {
       
    75 
       
    76       byStart = tracks.byStart[ start ];
       
    77       natives = byStart._natives;
       
    78       type = natives && natives.type;
       
    79 
       
    80       if ( !natives ||
       
    81           ( !!registryByName[ type ] || !!obj[ type ] ) ) {
       
    82 
       
    83         if ( ( byStart.start <= currentTime && byStart.end > currentTime ) &&
       
    84                 disabled.indexOf( type ) === -1 ) {
       
    85 
       
    86           if ( !byStart._running ) {
       
    87             byStart._running = true;
       
    88             natives.start.call( obj, null, byStart );
       
    89 
       
    90             // if the 'frameAnimation' option is used,
       
    91             // push the current byStart object into the `animating` cue
       
    92             if ( animation &&
       
    93                 ( byStart && byStart._running && byStart.natives.frame ) ) {
       
    94 
       
    95               natives.frame.call( obj, null, byStart, currentTime );
       
    96             }
       
    97           }
       
    98         } else if ( byStart._running === true ) {
       
    99 
       
   100           byStart._running = false;
       
   101           natives.end.call( obj, null, byStart );
       
   102 
       
   103           if ( animation && byStart._natives.frame ) {
       
   104             animIndex = animating.indexOf( byStart );
       
   105             if ( animIndex >= 0 ) {
       
   106               animating.splice( animIndex, 1 );
       
   107             }
       
   108           }
       
   109         }
       
   110       }
       
   111 
       
   112       start--;
       
   113     }
       
   114   },
       
   115 
       
   116   //  Declare constructor
       
   117   //  Returns an instance object.
       
   118   Popcorn = function( entity, options ) {
       
   119     //  Return new Popcorn object
       
   120     return new Popcorn.p.init( entity, options || null );
       
   121   };
       
   122 
       
   123   //  Popcorn API version, automatically inserted via build system.
       
   124   Popcorn.version = "@VERSION";
       
   125 
       
   126   //  Boolean flag allowing a client to determine if Popcorn can be supported
       
   127   Popcorn.isSupported = true;
       
   128 
       
   129   //  Instance caching
       
   130   Popcorn.instances = [];
       
   131 
       
   132   //  Declare a shortcut (Popcorn.p) to and a definition of
       
   133   //  the new prototype for our Popcorn constructor
       
   134   Popcorn.p = Popcorn.prototype = {
       
   135 
       
   136     init: function( entity, options ) {
       
   137 
       
   138       var matches;
       
   139 
       
   140       //  Supports Popcorn(function () { /../ })
       
   141       //  Originally proposed by Daniel Brooks
       
   142 
       
   143       if ( typeof entity === "function" ) {
       
   144 
       
   145         //  If document ready has already fired
       
   146         if ( document.readyState === "interactive" || document.readyState === "complete" ) {
       
   147 
       
   148           entity( document, Popcorn );
       
   149 
       
   150           return;
       
   151         }
       
   152         //  Add `entity` fn to ready stack
       
   153         readyStack.push( entity );
       
   154 
       
   155         //  This process should happen once per page load
       
   156         if ( !readyBound ) {
       
   157 
       
   158           //  set readyBound flag
       
   159           readyBound = true;
       
   160 
       
   161           var DOMContentLoaded  = function() {
       
   162 
       
   163             readyFired = true;
       
   164 
       
   165             //  Remove global DOM ready listener
       
   166             document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
       
   167 
       
   168             //  Execute all ready function in the stack
       
   169             for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
       
   170 
       
   171               readyStack[ i ].call( document, Popcorn );
       
   172 
       
   173             }
       
   174             //  GC readyStack
       
   175             readyStack = null;
       
   176           };
       
   177 
       
   178           //  Register global DOM ready listener
       
   179           document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
       
   180         }
       
   181 
       
   182         return;
       
   183       }
       
   184 
       
   185       //  Check if entity is a valid string id
       
   186       matches = rIdExp.exec( entity );
       
   187 
       
   188       //  Get media element by id or object reference
       
   189       this.media = matches && matches.length && matches[ 2 ] ?
       
   190                      document.getElementById( matches[ 2 ] ) :
       
   191                      entity;
       
   192 
       
   193       //  Create an audio or video element property reference
       
   194       this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media;
       
   195 
       
   196       //  Register new instance
       
   197       Popcorn.instances.push( this );
       
   198 
       
   199       this.options = options || {};
       
   200 
       
   201       this.isDestroyed = false;
       
   202 
       
   203       this.data = {
       
   204 
       
   205         // Executed by either timeupdate event or in rAF loop
       
   206         timeUpdate: Popcorn.nop,
       
   207 
       
   208         // Allows disabling a plugin per instance
       
   209         disabled: [],
       
   210 
       
   211         // Stores DOM event queues by type
       
   212         events: {},
       
   213 
       
   214         // Stores Special event hooks data
       
   215         hooks: {},
       
   216 
       
   217         // Store track event history data
       
   218         history: [],
       
   219 
       
   220         // Stores ad-hoc state related data]
       
   221         state: {
       
   222           volume: this.media.volume
       
   223         },
       
   224 
       
   225         // Store track event object references by trackId
       
   226         trackRefs: {},
       
   227 
       
   228         // Playback track event queues
       
   229         trackEvents: {
       
   230           byStart: [{
       
   231 
       
   232             start: -1,
       
   233             end: -1
       
   234           }],
       
   235           byEnd: [{
       
   236             start: -1,
       
   237             end: -1
       
   238           }],
       
   239           animating: [],
       
   240           startIndex: 0,
       
   241           endIndex: 0,
       
   242           previousUpdateTime: -1
       
   243         }
       
   244       };
       
   245 
       
   246       //  Wrap true ready check
       
   247       var isReady = function( that ) {
       
   248 
       
   249         var duration, videoDurationPlus;
       
   250 
       
   251         if ( that.media.readyState >= 2 ) {
       
   252           //  Adding padding to the front and end of the arrays
       
   253           //  this is so we do not fall off either end
       
   254 
       
   255           duration = that.media.duration;
       
   256           //  Check for no duration info (NaN)
       
   257           videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
       
   258 
       
   259           Popcorn.addTrackEvent( that, {
       
   260             start: videoDurationPlus,
       
   261             end: videoDurationPlus
       
   262           });
       
   263 
       
   264           if ( that.options.frameAnimation ) {
       
   265             //  if Popcorn is created with frameAnimation option set to true,
       
   266             //  requestAnimFrame is used instead of "timeupdate" media event.
       
   267             //  This is for greater frame time accuracy, theoretically up to
       
   268             //  60 frames per second as opposed to ~4 ( ~every 15-250ms)
       
   269             that.data.timeUpdate = function () {
       
   270 
       
   271               Popcorn.timeUpdate( that, {} );
       
   272 
       
   273               that.trigger( "timeupdate" );
       
   274 
       
   275               !that.isDestroyed && requestAnimFrame( that.data.timeUpdate );
       
   276             };
       
   277 
       
   278             !that.isDestroyed && requestAnimFrame( that.data.timeUpdate );
       
   279 
       
   280           } else {
       
   281 
       
   282             that.data.timeUpdate = function( event ) {
       
   283               Popcorn.timeUpdate( that, event );
       
   284             };
       
   285 
       
   286             if ( !that.isDestroyed ) {
       
   287               that.media.addEventListener( "timeupdate", that.data.timeUpdate, false );
       
   288             }
       
   289           }
       
   290         } else {
       
   291           global.setTimeout(function() {
       
   292             isReady( that );
       
   293           }, 1 );
       
   294         }
       
   295       };
       
   296 
       
   297       isReady( this );
       
   298 
       
   299       return this;
       
   300     }
       
   301   };
       
   302 
       
   303   //  Extend constructor prototype to instance prototype
       
   304   //  Allows chaining methods to instances
       
   305   Popcorn.p.init.prototype = Popcorn.p;
       
   306 
       
   307   Popcorn.forEach = function( obj, fn, context ) {
       
   308 
       
   309     if ( !obj || !fn ) {
       
   310       return {};
       
   311     }
       
   312 
       
   313     context = context || this;
       
   314 
       
   315     var key, len;
       
   316 
       
   317     // Use native whenever possible
       
   318     if ( forEach && obj.forEach === forEach ) {
       
   319       return obj.forEach( fn, context );
       
   320     }
       
   321 
       
   322     if ( toString.call( obj ) === "[object NodeList]" ) {
       
   323       for ( key = 0, len = obj.length; key < len; key++ ) {
       
   324         fn.call( context, obj[ key ], key, obj );
       
   325       }
       
   326       return obj;
       
   327     }
       
   328 
       
   329     for ( key in obj ) {
       
   330       if ( hasOwn.call( obj, key ) ) {
       
   331         fn.call( context, obj[ key ], key, obj );
       
   332       }
       
   333     }
       
   334     return obj;
       
   335   };
       
   336 
       
   337   Popcorn.extend = function( obj ) {
       
   338     var dest = obj, src = slice.call( arguments, 1 );
       
   339 
       
   340     Popcorn.forEach( src, function( copy ) {
       
   341       for ( var prop in copy ) {
       
   342         dest[ prop ] = copy[ prop ];
       
   343       }
       
   344     });
       
   345 
       
   346     return dest;
       
   347   };
       
   348 
       
   349 
       
   350   // A Few reusable utils, memoized onto Popcorn
       
   351   Popcorn.extend( Popcorn, {
       
   352     noConflict: function( deep ) {
       
   353 
       
   354       if ( deep ) {
       
   355         global.Popcorn = _Popcorn;
       
   356       }
       
   357 
       
   358       return Popcorn;
       
   359     },
       
   360     error: function( msg ) {
       
   361       throw new Error( msg );
       
   362     },
       
   363     guid: function( prefix ) {
       
   364       Popcorn.guid.counter++;
       
   365       return  ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
       
   366     },
       
   367     sizeOf: function( obj ) {
       
   368       var size = 0;
       
   369 
       
   370       for ( var prop in obj ) {
       
   371         size++;
       
   372       }
       
   373 
       
   374       return size;
       
   375     },
       
   376     isArray: Array.isArray || function( array ) {
       
   377       return toString.call( array ) === "[object Array]";
       
   378     },
       
   379 
       
   380     nop: function() {},
       
   381 
       
   382     position: function( elem ) {
       
   383 
       
   384       var clientRect = elem.getBoundingClientRect(),
       
   385           bounds = {},
       
   386           doc = elem.ownerDocument,
       
   387           docElem = document.documentElement,
       
   388           body = document.body,
       
   389           clientTop, clientLeft, scrollTop, scrollLeft, top, left;
       
   390 
       
   391       //  Determine correct clientTop/Left
       
   392       clientTop = docElem.clientTop || body.clientTop || 0;
       
   393       clientLeft = docElem.clientLeft || body.clientLeft || 0;
       
   394 
       
   395       //  Determine correct scrollTop/Left
       
   396       scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
       
   397       scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
       
   398 
       
   399       //  Temp top/left
       
   400       top = Math.ceil( clientRect.top + scrollTop - clientTop );
       
   401       left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
       
   402 
       
   403       for ( var p in clientRect ) {
       
   404         bounds[ p ] = Math.round( clientRect[ p ] );
       
   405       }
       
   406 
       
   407       return Popcorn.extend({}, bounds, { top: top, left: left });
       
   408     },
       
   409 
       
   410     disable: function( instance, plugin ) {
       
   411 
       
   412       var disabled = instance.data.disabled;
       
   413 
       
   414       if ( disabled.indexOf( plugin ) === -1 ) {
       
   415         disabled.push( plugin );
       
   416       }
       
   417 
       
   418       refresh( instance );
       
   419 
       
   420       return instance;
       
   421     },
       
   422     enable: function( instance, plugin ) {
       
   423 
       
   424       var disabled = instance.data.disabled,
       
   425           index = disabled.indexOf( plugin );
       
   426 
       
   427       if ( index > -1 ) {
       
   428         disabled.splice( index, 1 );
       
   429       }
       
   430 
       
   431       refresh( instance );
       
   432 
       
   433       return instance;
       
   434     },
       
   435     destroy: function( instance ) {
       
   436       var events = instance.data.events,
       
   437           singleEvent, item, fn;
       
   438 
       
   439       //  Iterate through all events and remove them
       
   440       for ( item in events ) {
       
   441         singleEvent = events[ item ];
       
   442         for ( fn in singleEvent ) {
       
   443           delete singleEvent[ fn ];
       
   444         }
       
   445         events[ item ] = null;
       
   446       }
       
   447 
       
   448       if ( !instance.isDestroyed ) {
       
   449         instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false );
       
   450         instance.isDestroyed = true;
       
   451       }
       
   452     }
       
   453   });
       
   454 
       
   455   //  Memoized GUID Counter
       
   456   Popcorn.guid.counter = 1;
       
   457 
       
   458   //  Factory to implement getters, setters and controllers
       
   459   //  as Popcorn instance methods. The IIFE will create and return
       
   460   //  an object with defined methods
       
   461   Popcorn.extend(Popcorn.p, (function() {
       
   462 
       
   463       var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
       
   464                     "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
       
   465           ret = {};
       
   466 
       
   467 
       
   468       //  Build methods, store in object that is returned and passed to extend
       
   469       Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
       
   470 
       
   471         ret[ name ] = function( arg ) {
       
   472 
       
   473           if ( typeof this.media[ name ] === "function" ) {
       
   474 
       
   475             // Support for shorthanded play(n)/pause(n) jump to currentTime
       
   476             // If arg is not null or undefined and called by one of the
       
   477             // allowed shorthandable methods, then set the currentTime
       
   478             // Supports time as seconds or SMPTE
       
   479             if ( arg != null && /play|pause/.test( name ) ) {
       
   480               this.media.currentTime = Popcorn.util.toSeconds( arg );
       
   481             }
       
   482 
       
   483             this.media[ name ]();
       
   484 
       
   485             return this;
       
   486           }
       
   487 
       
   488 
       
   489           if ( arg != null ) {
       
   490 
       
   491             this.media[ name ] = arg;
       
   492 
       
   493             return this;
       
   494           }
       
   495 
       
   496           return this.media[ name ];
       
   497         };
       
   498       });
       
   499 
       
   500       return ret;
       
   501 
       
   502     })()
       
   503   );
       
   504 
       
   505   Popcorn.forEach( "enable disable".split(" "), function( method ) {
       
   506     Popcorn.p[ method ] = function( plugin ) {
       
   507       return Popcorn[ method ]( this, plugin );
       
   508     };
       
   509   });
       
   510 
       
   511   Popcorn.extend(Popcorn.p, {
       
   512 
       
   513     //  Rounded currentTime
       
   514     roundTime: function() {
       
   515       return -~this.media.currentTime;
       
   516     },
       
   517 
       
   518     //  Attach an event to a single point in time
       
   519     exec: function( time, fn ) {
       
   520 
       
   521       //  Creating a one second track event with an empty end
       
   522       Popcorn.addTrackEvent( this, {
       
   523         start: time,
       
   524         end: time + 1,
       
   525         _running: false,
       
   526         _natives: {
       
   527           start: fn || Popcorn.nop,
       
   528           end: Popcorn.nop,
       
   529           type: "exec"
       
   530         }
       
   531       });
       
   532 
       
   533       return this;
       
   534     },
       
   535 
       
   536     // Mute the calling media, optionally toggle
       
   537     mute: function( toggle ) {
       
   538 
       
   539       var event = toggle == null || toggle === true ? "muted" : "unmuted";
       
   540 
       
   541       // If `toggle` is explicitly `false`,
       
   542       // unmute the media and restore the volume level
       
   543       if ( event === "unmuted" ) {
       
   544         this.media.muted = false;
       
   545         this.media.volume = this.data.state.volume;
       
   546       }
       
   547 
       
   548       // If `toggle` is either null or undefined,
       
   549       // save the current volume and mute the media element
       
   550       if ( event === "muted" ) {
       
   551         this.data.state.volume = this.media.volume;
       
   552         this.media.muted = true;
       
   553       }
       
   554 
       
   555       // Trigger either muted|unmuted event
       
   556       this.trigger( event );
       
   557 
       
   558       return this;
       
   559     },
       
   560 
       
   561     // Convenience method, unmute the calling media
       
   562     unmute: function( toggle ) {
       
   563 
       
   564       return this.mute( toggle == null ? false : !toggle );
       
   565     },
       
   566 
       
   567     // Get the client bounding box of an instance element
       
   568     position: function() {
       
   569       return Popcorn.position( this.media );
       
   570     },
       
   571 
       
   572     // Toggle a plugin's playback behaviour (on or off) per instance
       
   573     toggle: function( plugin ) {
       
   574       return Popcorn[ this.data.disabled.indexOf( plugin ) > -1 ? "enable" : "disable" ]( this, plugin );
       
   575     },
       
   576 
       
   577     // Set default values for plugin options objects per instance
       
   578     defaults: function( plugin, defaults ) {
       
   579 
       
   580       // If an array of default configurations is provided,
       
   581       // iterate and apply each to this instance
       
   582       if ( Popcorn.isArray( plugin ) ) {
       
   583 
       
   584         Popcorn.forEach( plugin, function( obj ) {
       
   585           for ( var name in obj ) {
       
   586             this.defaults( name, obj[ name ] );
       
   587           }
       
   588         }, this );
       
   589 
       
   590         return this;
       
   591       }
       
   592 
       
   593       if ( !this.options.defaults ) {
       
   594         this.options.defaults = {};
       
   595       }
       
   596 
       
   597       if ( !this.options.defaults[ plugin ] ) {
       
   598         this.options.defaults[ plugin ] = {};
       
   599       }
       
   600 
       
   601       Popcorn.extend( this.options.defaults[ plugin ], defaults );
       
   602 
       
   603       return this;
       
   604     }
       
   605   });
       
   606 
       
   607   Popcorn.Events  = {
       
   608     UIEvents: "blur focus focusin focusout load resize scroll unload",
       
   609     MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
       
   610     Events: "loadstart progress suspend emptied stalled play pause " +
       
   611             "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
       
   612             "seeking seeked timeupdate ended ratechange durationchange volumechange"
       
   613   };
       
   614 
       
   615   Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
       
   616                            Popcorn.Events.MouseEvents + " " +
       
   617                            Popcorn.Events.Events;
       
   618 
       
   619   internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
       
   620 
       
   621   // Privately compile events table at load time
       
   622   (function( events, data ) {
       
   623 
       
   624     var apis = internal.events.apiTypes,
       
   625     eventsList = events.Natives.split( /\s+/g ),
       
   626     idx = 0, len = eventsList.length, prop;
       
   627 
       
   628     for( ; idx < len; idx++ ) {
       
   629       data.hash[ eventsList[idx] ] = true;
       
   630     }
       
   631 
       
   632     apis.forEach(function( val, idx ) {
       
   633 
       
   634       data.apis[ val ] = {};
       
   635 
       
   636       var apiEvents = events[ val ].split( /\s+/g ),
       
   637       len = apiEvents.length,
       
   638       k = 0;
       
   639 
       
   640       for ( ; k < len; k++ ) {
       
   641         data.apis[ val ][ apiEvents[ k ] ] = true;
       
   642       }
       
   643     });
       
   644   })( Popcorn.Events, internal.events );
       
   645 
       
   646   Popcorn.events = {
       
   647 
       
   648     isNative: function( type ) {
       
   649       return !!internal.events.hash[ type ];
       
   650     },
       
   651     getInterface: function( type ) {
       
   652 
       
   653       if ( !Popcorn.events.isNative( type ) ) {
       
   654         return false;
       
   655       }
       
   656 
       
   657       var eventApi = internal.events,
       
   658         apis = eventApi.apiTypes,
       
   659         apihash = eventApi.apis,
       
   660         idx = 0, len = apis.length, api, tmp;
       
   661 
       
   662       for ( ; idx < len; idx++ ) {
       
   663         tmp = apis[ idx ];
       
   664 
       
   665         if ( apihash[ tmp ][ type ] ) {
       
   666           api = tmp;
       
   667           break;
       
   668         }
       
   669       }
       
   670       return api;
       
   671     },
       
   672     //  Compile all native events to single array
       
   673     all: Popcorn.Events.Natives.split( /\s+/g ),
       
   674     //  Defines all Event handling static functions
       
   675     fn: {
       
   676       trigger: function( type, data ) {
       
   677 
       
   678         var eventInterface, evt;
       
   679         //  setup checks for custom event system
       
   680         if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
       
   681 
       
   682           eventInterface  = Popcorn.events.getInterface( type );
       
   683 
       
   684           if ( eventInterface ) {
       
   685 
       
   686             evt = document.createEvent( eventInterface );
       
   687             evt.initEvent( type, true, true, global, 1 );
       
   688 
       
   689             this.media.dispatchEvent( evt );
       
   690 
       
   691             return this;
       
   692           }
       
   693 
       
   694           //  Custom events
       
   695           Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
       
   696 
       
   697             obj.call( this, data );
       
   698 
       
   699           }, this );
       
   700 
       
   701         }
       
   702 
       
   703         return this;
       
   704       },
       
   705       listen: function( type, fn ) {
       
   706 
       
   707         var self = this,
       
   708             hasEvents = true,
       
   709             eventHook = Popcorn.events.hooks[ type ],
       
   710             origType = type,
       
   711             tmp;
       
   712 
       
   713         if ( !this.data.events[ type ] ) {
       
   714           this.data.events[ type ] = {};
       
   715           hasEvents = false;
       
   716         }
       
   717 
       
   718         // Check and setup event hooks
       
   719         if ( eventHook ) {
       
   720 
       
   721           // Execute hook add method if defined
       
   722           if ( eventHook.add ) {
       
   723             eventHook.add.call( this, {}, fn );
       
   724           }
       
   725 
       
   726           // Reassign event type to our piggyback event type if defined
       
   727           if ( eventHook.bind ) {
       
   728             type = eventHook.bind;
       
   729           }
       
   730 
       
   731           // Reassign handler if defined
       
   732           if ( eventHook.handler ) {
       
   733             tmp = fn;
       
   734 
       
   735             fn = function wrapper( event ) {
       
   736               eventHook.handler.call( self, event, tmp );
       
   737             };
       
   738           }
       
   739 
       
   740           // assume the piggy back event is registered
       
   741           hasEvents = true;
       
   742 
       
   743           // Setup event registry entry
       
   744           if ( !this.data.events[ type ] ) {
       
   745             this.data.events[ type ] = {};
       
   746             // Toggle if the previous assumption was untrue
       
   747             hasEvents = false;
       
   748           }
       
   749         }
       
   750 
       
   751         //  Register event and handler
       
   752         this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
       
   753 
       
   754         // only attach one event of any type
       
   755         if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
       
   756 
       
   757           this.media.addEventListener( type, function( event ) {
       
   758 
       
   759             Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
       
   760               if ( typeof obj === "function" ) {
       
   761                 obj.call( self, event );
       
   762               }
       
   763             });
       
   764 
       
   765           }, false);
       
   766         }
       
   767         return this;
       
   768       },
       
   769       unlisten: function( type, fn ) {
       
   770 
       
   771         if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
       
   772 
       
   773           delete this.data.events[ type ][ fn ];
       
   774 
       
   775           return this;
       
   776         }
       
   777 
       
   778         this.data.events[ type ] = null;
       
   779 
       
   780         return this;
       
   781       }
       
   782     },
       
   783     hooks: {
       
   784       canplayall: {
       
   785         bind: "canplaythrough",
       
   786         add: function( event, callback ) {
       
   787 
       
   788           var state = false;
       
   789 
       
   790           if ( this.media.readyState ) {
       
   791 
       
   792             callback.call( this, event );
       
   793 
       
   794             state = true;
       
   795           }
       
   796 
       
   797           this.data.hooks.canplayall = {
       
   798             fired: state
       
   799           };
       
   800         },
       
   801         // declare special handling instructions
       
   802         handler: function canplayall( event, callback ) {
       
   803 
       
   804           if ( !this.data.hooks.canplayall.fired ) {
       
   805             // trigger original user callback once
       
   806             callback.call( this, event );
       
   807 
       
   808             this.data.hooks.canplayall.fired = true;
       
   809           }
       
   810         }
       
   811       }
       
   812     }
       
   813   };
       
   814 
       
   815   //  Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
       
   816   Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) {
       
   817     Popcorn.p[ key ] = Popcorn.events.fn[ key ];
       
   818   });
       
   819 
       
   820   // Internal Only - Adds track events to the instance object
       
   821   Popcorn.addTrackEvent = function( obj, track ) {
       
   822 
       
   823     // Determine if this track has default options set for it
       
   824     // If so, apply them to the track object
       
   825     if ( track && track._natives && track._natives.type &&
       
   826         ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
       
   827 
       
   828       track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
       
   829     }
       
   830 
       
   831     if ( track._natives ) {
       
   832       //  Supports user defined track event id
       
   833       track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
       
   834 
       
   835       //  Push track event ids into the history
       
   836       obj.data.history.push( track._id );
       
   837     }
       
   838 
       
   839     track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
       
   840     track.end   = Popcorn.util.toSeconds( track.end, obj.options.framerate );
       
   841 
       
   842     //  Store this definition in an array sorted by times
       
   843     var byStart = obj.data.trackEvents.byStart,
       
   844         byEnd = obj.data.trackEvents.byEnd,
       
   845         startIndex, endIndex,
       
   846         currentTime;
       
   847 
       
   848     for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) {
       
   849 
       
   850       if ( track.start >= byStart[ startIndex ].start ) {
       
   851         byStart.splice( startIndex + 1, 0, track );
       
   852         break;
       
   853       }
       
   854     }
       
   855 
       
   856     for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) {
       
   857 
       
   858       if ( track.end > byEnd[ endIndex ].end ) {
       
   859         byEnd.splice( endIndex + 1, 0, track );
       
   860         break;
       
   861       }
       
   862     }
       
   863 
       
   864     // Display track event immediately if it's enabled and current
       
   865     if ( track._natives &&
       
   866         ( !!Popcorn.registryByName[ track._natives.type ] || !!obj[ track._natives.type ] ) ) {
       
   867 
       
   868       currentTime = obj.media.currentTime;
       
   869       if ( track.end > currentTime &&
       
   870         track.start <= currentTime &&
       
   871         obj.data.disabled.indexOf( track._natives.type ) === -1 ) {
       
   872 
       
   873         track._running = true;
       
   874         track._natives.start.call( obj, null, track );
       
   875 
       
   876         if ( obj.options.frameAnimation &&
       
   877           track._natives.frame ) {
       
   878 
       
   879           obj.data.trackEvents.animating.push( track );
       
   880           track._natives.frame.call( obj, null, track, currentTime );
       
   881         }
       
   882       }
       
   883     }
       
   884 
       
   885     // update startIndex and endIndex
       
   886     if ( startIndex <= obj.data.trackEvents.startIndex &&
       
   887       track.start <= obj.data.trackEvents.previousUpdateTime ) {
       
   888 
       
   889       obj.data.trackEvents.startIndex++;
       
   890     }
       
   891 
       
   892     if ( endIndex <= obj.data.trackEvents.endIndex &&
       
   893       track.end < obj.data.trackEvents.previousUpdateTime ) {
       
   894 
       
   895       obj.data.trackEvents.endIndex++;
       
   896     }
       
   897 
       
   898     this.timeUpdate( obj, null, true );
       
   899 
       
   900     // Store references to user added trackevents in ref table
       
   901     if ( track._id ) {
       
   902       Popcorn.addTrackEvent.ref( obj, track );
       
   903     }
       
   904   };
       
   905 
       
   906   // Internal Only - Adds track event references to the instance object's trackRefs hash table
       
   907   Popcorn.addTrackEvent.ref = function( obj, track ) {
       
   908     obj.data.trackRefs[ track._id ] = track;
       
   909 
       
   910     return obj;
       
   911   };
       
   912 
       
   913   Popcorn.removeTrackEvent  = function( obj, trackId ) {
       
   914 
       
   915     var historyLen = obj.data.history.length,
       
   916         indexWasAt = 0,
       
   917         byStart = [],
       
   918         byEnd = [],
       
   919         animating = [],
       
   920         history = [];
       
   921 
       
   922     Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
       
   923       // Preserve the original start/end trackEvents
       
   924       if ( !o._id ) {
       
   925         byStart.push( obj.data.trackEvents.byStart[i] );
       
   926         byEnd.push( obj.data.trackEvents.byEnd[i] );
       
   927       }
       
   928 
       
   929       // Filter for user track events (vs system track events)
       
   930       if ( o._id ) {
       
   931 
       
   932         // Filter for the trackevent to remove
       
   933         if ( o._id !== trackId ) {
       
   934           byStart.push( obj.data.trackEvents.byStart[i] );
       
   935           byEnd.push( obj.data.trackEvents.byEnd[i] );
       
   936         }
       
   937 
       
   938         //  Capture the position of the track being removed.
       
   939         if ( o._id === trackId ) {
       
   940           indexWasAt = i;
       
   941           o._natives._teardown && o._natives._teardown.call( obj, o );
       
   942         }
       
   943       }
       
   944 
       
   945     });
       
   946 
       
   947     if ( obj.data.trackEvents.animating.length ) {
       
   948       Popcorn.forEach( obj.data.trackEvents.animating, function( o, i, context ) {
       
   949         // Preserve the original start/end trackEvents
       
   950         if ( !o._id ) {
       
   951           animating.push( obj.data.trackEvents.animating[i] );
       
   952         }
       
   953 
       
   954         // Filter for user track events (vs system track events)
       
   955         if ( o._id ) {
       
   956           // Filter for the trackevent to remove
       
   957           if ( o._id !== trackId ) {
       
   958             animating.push( obj.data.trackEvents.animating[i] );
       
   959           }
       
   960         }
       
   961       });
       
   962     }
       
   963 
       
   964     //  Update
       
   965     if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
       
   966       obj.data.trackEvents.startIndex--;
       
   967     }
       
   968 
       
   969     if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
       
   970       obj.data.trackEvents.endIndex--;
       
   971     }
       
   972 
       
   973     obj.data.trackEvents.byStart = byStart;
       
   974     obj.data.trackEvents.byEnd = byEnd;
       
   975     obj.data.trackEvents.animating = animating;
       
   976 
       
   977     for ( var i = 0; i < historyLen; i++ ) {
       
   978       if ( obj.data.history[ i ] !== trackId ) {
       
   979         history.push( obj.data.history[ i ] );
       
   980       }
       
   981     }
       
   982 
       
   983     // Update ordered history array
       
   984     obj.data.history = history;
       
   985 
       
   986     // Update track event references
       
   987     Popcorn.removeTrackEvent.ref( obj, trackId );
       
   988   };
       
   989 
       
   990   // Internal Only - Removes track event references from instance object's trackRefs hash table
       
   991   Popcorn.removeTrackEvent.ref = function( obj, trackId ) {
       
   992     delete obj.data.trackRefs[ trackId ];
       
   993 
       
   994     return obj;
       
   995   };
       
   996 
       
   997   // Return an array of track events bound to this instance object
       
   998   Popcorn.getTrackEvents = function( obj ) {
       
   999 
       
  1000     var trackevents = [],
       
  1001       refs = obj.data.trackEvents.byStart,
       
  1002       length = refs.length,
       
  1003       idx = 0,
       
  1004       ref;
       
  1005 
       
  1006     for ( ; idx < length; idx++ ) {
       
  1007       ref = refs[ idx ];
       
  1008       // Return only user attributed track event references
       
  1009       if ( ref._id ) {
       
  1010         trackevents.push( ref );
       
  1011       }
       
  1012     }
       
  1013 
       
  1014     return trackevents;
       
  1015   };
       
  1016 
       
  1017   // Internal Only - Returns an instance object's trackRefs hash table
       
  1018   Popcorn.getTrackEvents.ref = function( obj ) {
       
  1019     return obj.data.trackRefs;
       
  1020   };
       
  1021 
       
  1022   // Return a single track event bound to this instance object
       
  1023   Popcorn.getTrackEvent = function( obj, trackId ) {
       
  1024     return obj.data.trackRefs[ trackId ];
       
  1025   };
       
  1026 
       
  1027   // Internal Only - Returns an instance object's track reference by track id
       
  1028   Popcorn.getTrackEvent.ref = function( obj, trackId ) {
       
  1029     return obj.data.trackRefs[ trackId ];
       
  1030   };
       
  1031 
       
  1032   Popcorn.getLastTrackEventId = function( obj ) {
       
  1033     return obj.data.history[ obj.data.history.length - 1 ];
       
  1034   };
       
  1035 
       
  1036   Popcorn.timeUpdate = function( obj, event ) {
       
  1037 
       
  1038     var currentTime = obj.media.currentTime,
       
  1039         previousTime = obj.data.trackEvents.previousUpdateTime,
       
  1040         tracks = obj.data.trackEvents,
       
  1041         animating = tracks.animating,
       
  1042         end = tracks.endIndex,
       
  1043         start = tracks.startIndex,
       
  1044         animIndex = 0,
       
  1045 
       
  1046         registryByName = Popcorn.registryByName,
       
  1047 
       
  1048         byEnd, byStart, byAnimate, natives, type;
       
  1049 
       
  1050     //  Playbar advancing
       
  1051     if ( previousTime <= currentTime ) {
       
  1052 
       
  1053       while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
       
  1054 
       
  1055         byEnd = tracks.byEnd[ end ];
       
  1056         natives = byEnd._natives;
       
  1057         type = natives && natives.type;
       
  1058 
       
  1059         //  If plugin does not exist on this instance, remove it
       
  1060         if ( !natives ||
       
  1061             ( !!registryByName[ type ] ||
       
  1062               !!obj[ type ] ) ) {
       
  1063 
       
  1064           if ( byEnd._running === true ) {
       
  1065             byEnd._running = false;
       
  1066             natives.end.call( obj, event, byEnd );
       
  1067           }
       
  1068 
       
  1069           end++;
       
  1070         } else {
       
  1071           // remove track event
       
  1072           Popcorn.removeTrackEvent( obj, byEnd._id );
       
  1073           return;
       
  1074         }
       
  1075       }
       
  1076 
       
  1077       while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
       
  1078 
       
  1079         byStart = tracks.byStart[ start ];
       
  1080         natives = byStart._natives;
       
  1081         type = natives && natives.type;
       
  1082 
       
  1083         //  If plugin does not exist on this instance, remove it
       
  1084         if ( !natives ||
       
  1085             ( !!registryByName[ type ] ||
       
  1086               !!obj[ type ] ) ) {
       
  1087 
       
  1088           if ( byStart.end > currentTime &&
       
  1089                 byStart._running === false &&
       
  1090                   obj.data.disabled.indexOf( type ) === -1 ) {
       
  1091 
       
  1092             byStart._running = true;
       
  1093             natives.start.call( obj, event, byStart );
       
  1094 
       
  1095             // If the `frameAnimation` option is used,
       
  1096             // push the current byStart object into the `animating` cue
       
  1097             if ( obj.options.frameAnimation &&
       
  1098                 ( byStart && byStart._running && byStart._natives.frame ) ) {
       
  1099 
       
  1100               animating.push( byStart );
       
  1101             }
       
  1102           }
       
  1103           start++;
       
  1104         } else {
       
  1105           // remove track event
       
  1106           Popcorn.removeTrackEvent( obj, byStart._id );
       
  1107           return;
       
  1108         }
       
  1109       }
       
  1110 
       
  1111       // If the `frameAnimation` option is used, iterate the animating track
       
  1112       // and execute the `frame` callback
       
  1113       if ( obj.options.frameAnimation ) {
       
  1114         while ( animIndex < animating.length ) {
       
  1115 
       
  1116           byAnimate = animating[ animIndex ];
       
  1117 
       
  1118           if ( !byAnimate._running ) {
       
  1119             animating.splice( animIndex, 1 );
       
  1120           } else {
       
  1121             byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
       
  1122             animIndex++;
       
  1123           }
       
  1124         }
       
  1125       }
       
  1126 
       
  1127     // Playbar receding
       
  1128     } else if ( previousTime > currentTime ) {
       
  1129 
       
  1130       while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
       
  1131 
       
  1132         byStart = tracks.byStart[ start ];
       
  1133         natives = byStart._natives;
       
  1134         type = natives && natives.type;
       
  1135 
       
  1136         // if plugin does not exist on this instance, remove it
       
  1137         if ( !natives ||
       
  1138             ( !!registryByName[ type ] ||
       
  1139               !!obj[ type ] ) ) {
       
  1140 
       
  1141           if ( byStart._running === true ) {
       
  1142             byStart._running = false;
       
  1143             natives.end.call( obj, event, byStart );
       
  1144           }
       
  1145           start--;
       
  1146         } else {
       
  1147           // remove track event
       
  1148           Popcorn.removeTrackEvent( obj, byStart._id );
       
  1149           return;
       
  1150         }
       
  1151       }
       
  1152 
       
  1153       while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
       
  1154 
       
  1155         byEnd = tracks.byEnd[ end ];
       
  1156         natives = byEnd._natives;
       
  1157         type = natives && natives.type;
       
  1158 
       
  1159         // if plugin does not exist on this instance, remove it
       
  1160         if ( !natives ||
       
  1161             ( !!registryByName[ type ] ||
       
  1162               !!obj[ type ] ) ) {
       
  1163 
       
  1164           if ( byEnd.start <= currentTime &&
       
  1165                 byEnd._running === false  &&
       
  1166                   obj.data.disabled.indexOf( type ) === -1 ) {
       
  1167 
       
  1168             byEnd._running = true;
       
  1169             natives.start.call( obj, event, byEnd );
       
  1170 
       
  1171             // If the `frameAnimation` option is used,
       
  1172             // push the current byEnd object into the `animating` cue
       
  1173             if ( obj.options.frameAnimation &&
       
  1174                   ( byEnd && byEnd._running && byEnd._natives.frame ) ) {
       
  1175 
       
  1176               animating.push( byEnd );
       
  1177             }
       
  1178           }
       
  1179           end--;
       
  1180         } else {
       
  1181           // remove track event
       
  1182           Popcorn.removeTrackEvent( obj, byEnd._id );
       
  1183           return;
       
  1184         }
       
  1185       }
       
  1186 
       
  1187       // If the `frameAnimation` option is used, iterate the animating track
       
  1188       // and execute the `frame` callback
       
  1189       if ( obj.options.frameAnimation ) {
       
  1190         while ( animIndex < animating.length ) {
       
  1191 
       
  1192           byAnimate = animating[ animIndex ];
       
  1193 
       
  1194           if ( !byAnimate._running ) {
       
  1195             animating.splice( animIndex, 1 );
       
  1196           } else {
       
  1197             byAnimate._natives.frame.call( obj, event, byAnimate, currentTime );
       
  1198             animIndex++;
       
  1199           }
       
  1200         }
       
  1201       }
       
  1202     // time bar is not moving ( video is paused )
       
  1203     }
       
  1204 
       
  1205     tracks.endIndex = end;
       
  1206     tracks.startIndex = start;
       
  1207     tracks.previousUpdateTime = currentTime;
       
  1208   };
       
  1209 
       
  1210   //  Map and Extend TrackEvent functions to all Popcorn instances
       
  1211   Popcorn.extend( Popcorn.p, {
       
  1212 
       
  1213     getTrackEvents: function() {
       
  1214       return Popcorn.getTrackEvents.call( null, this );
       
  1215     },
       
  1216 
       
  1217     getTrackEvent: function( id ) {
       
  1218       return Popcorn.getTrackEvent.call( null, this, id );
       
  1219     },
       
  1220 
       
  1221     getLastTrackEventId: function() {
       
  1222       return Popcorn.getLastTrackEventId.call( null, this );
       
  1223     },
       
  1224 
       
  1225     removeTrackEvent: function( id ) {
       
  1226 
       
  1227       Popcorn.removeTrackEvent.call( null, this, id );
       
  1228       return this;
       
  1229     },
       
  1230 
       
  1231     removePlugin: function( name ) {
       
  1232       Popcorn.removePlugin.call( null, this, name );
       
  1233       return this;
       
  1234     },
       
  1235 
       
  1236     timeUpdate: function( event ) {
       
  1237       Popcorn.timeUpdate.call( null, this, event );
       
  1238       return this;
       
  1239     },
       
  1240 
       
  1241     destroy: function() {
       
  1242       Popcorn.destroy.call( null, this );
       
  1243       return this;
       
  1244     }
       
  1245   });
       
  1246 
       
  1247   //  Plugin manifests
       
  1248   Popcorn.manifest = {};
       
  1249   //  Plugins are registered
       
  1250   Popcorn.registry = [];
       
  1251   Popcorn.registryByName = {};
       
  1252   //  An interface for extending Popcorn
       
  1253   //  with plugin functionality
       
  1254   Popcorn.plugin = function( name, definition, manifest ) {
       
  1255 
       
  1256     if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  1257       Popcorn.error( "'" + name + "' is a protected function name" );
       
  1258       return;
       
  1259     }
       
  1260 
       
  1261     //  Provides some sugar, but ultimately extends
       
  1262     //  the definition into Popcorn.p
       
  1263     var reserved = [ "start", "end" ],
       
  1264         plugin = {},
       
  1265         setup,
       
  1266         isfn = typeof definition === "function",
       
  1267         methods = [ "_setup", "_teardown", "start", "end", "frame" ];
       
  1268 
       
  1269     // combines calls of two function calls into one
       
  1270     var combineFn = function( first, second ) {
       
  1271 
       
  1272       first = first || Popcorn.nop;
       
  1273       second = second || Popcorn.nop;
       
  1274 
       
  1275       return function() {
       
  1276         first.apply( this, arguments );
       
  1277         second.apply( this, arguments );
       
  1278       };
       
  1279     };
       
  1280 
       
  1281     //  If `manifest` arg is undefined, check for manifest within the `definition` object
       
  1282     //  If no `definition.manifest`, an empty object is a sufficient fallback
       
  1283     Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
       
  1284 
       
  1285     // apply safe, and empty default functions
       
  1286     methods.forEach(function( method ) {
       
  1287       definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
       
  1288     });
       
  1289 
       
  1290     var pluginFn = function( setup, options ) {
       
  1291 
       
  1292       if ( !options ) {
       
  1293         return this;
       
  1294       }
       
  1295 
       
  1296       //  Storing the plugin natives
       
  1297       var natives = options._natives = {},
       
  1298           compose = "",
       
  1299           defaults, originalOpts, manifestOpts, mergedSetupOpts;
       
  1300 
       
  1301       Popcorn.extend( natives, setup );
       
  1302 
       
  1303       options._natives.type = name;
       
  1304       options._running = false;
       
  1305 
       
  1306       natives.start = natives.start || natives[ "in" ];
       
  1307       natives.end = natives.end || natives[ "out" ];
       
  1308 
       
  1309       // extend teardown to always call end if running
       
  1310       natives._teardown = combineFn(function() {
       
  1311 
       
  1312         var args = slice.call( arguments );
       
  1313 
       
  1314         // end function signature is not the same as teardown,
       
  1315         // put null on the front of arguments for the event parameter
       
  1316         args.unshift( null );
       
  1317 
       
  1318         // only call end if event is running
       
  1319         args[ 1 ]._running && natives.end.apply( this, args );
       
  1320       }, natives._teardown );
       
  1321 
       
  1322       // Check for previously set default options
       
  1323       defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ];
       
  1324 
       
  1325       // default to an empty string if no effect exists
       
  1326       // split string into an array of effects
       
  1327       options.compose = options.compose && options.compose.split( " " ) || [];
       
  1328       options.effect = options.effect && options.effect.split( " " ) || [];
       
  1329 
       
  1330       // join the two arrays together
       
  1331       options.compose = options.compose.concat( options.effect );
       
  1332 
       
  1333       options.compose.forEach(function( composeOption ) {
       
  1334 
       
  1335         // if the requested compose is garbage, throw it away
       
  1336         compose = Popcorn.compositions[ composeOption ] || {};
       
  1337 
       
  1338         // extends previous functions with compose function
       
  1339         methods.forEach(function( method ) {
       
  1340           natives[ method ] = combineFn( natives[ method ], compose[ method ] );
       
  1341         });
       
  1342       });
       
  1343 
       
  1344       //  Ensure a manifest object, an empty object is a sufficient fallback
       
  1345       options._natives.manifest = manifest;
       
  1346 
       
  1347       //  Checks for expected properties
       
  1348       if ( !( "start" in options ) ) {
       
  1349         options.start = options[ "in" ] || 0;
       
  1350       }
       
  1351 
       
  1352       if ( !( "end" in options ) ) {
       
  1353         options.end = options[ "out" ] || this.duration() || Number.MAX_VALUE;
       
  1354       }
       
  1355 
       
  1356       // Merge with defaults if they exist, make sure per call is prioritized
       
  1357       mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) :
       
  1358                           options;
       
  1359 
       
  1360       // Resolves 239, 241, 242
       
  1361       if ( !mergedSetupOpts.target ) {
       
  1362 
       
  1363         //  Sometimes the manifest may be missing entirely
       
  1364         //  or it has an options object that doesn't have a `target` property
       
  1365         manifestOpts = "options" in manifest && manifest.options;
       
  1366 
       
  1367         mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
       
  1368       }
       
  1369 
       
  1370       // Trigger _setup method if exists
       
  1371       options._natives._setup && options._natives._setup.call( this, mergedSetupOpts );
       
  1372 
       
  1373       // Create new track event for this instance
       
  1374       Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) );
       
  1375 
       
  1376       //  Future support for plugin event definitions
       
  1377       //  for all of the native events
       
  1378       Popcorn.forEach( setup, function( callback, type ) {
       
  1379 
       
  1380         if ( type !== "type" ) {
       
  1381 
       
  1382           if ( reserved.indexOf( type ) === -1 ) {
       
  1383 
       
  1384             this.listen( type, callback );
       
  1385           }
       
  1386         }
       
  1387 
       
  1388       }, this );
       
  1389 
       
  1390       return this;
       
  1391     };
       
  1392 
       
  1393     //  Assign new named definition
       
  1394     plugin[ name ] = function( options ) {
       
  1395       return pluginFn.call( this, isfn ? definition.call( this, options ) : definition,
       
  1396                                   options );
       
  1397     };
       
  1398 
       
  1399     //  Extend Popcorn.p with new named definition
       
  1400     Popcorn.extend( Popcorn.p, plugin );
       
  1401 
       
  1402     //  Push into the registry
       
  1403     var entry = {
       
  1404       fn: plugin[ name ],
       
  1405       definition: definition,
       
  1406       base: definition,
       
  1407       parents: [],
       
  1408       name: name
       
  1409     };
       
  1410     Popcorn.registry.push(
       
  1411        Popcorn.extend( plugin, entry, {
       
  1412         type: name
       
  1413       })
       
  1414     );
       
  1415     Popcorn.registryByName[ name ] = entry;
       
  1416 
       
  1417     return plugin;
       
  1418   };
       
  1419 
       
  1420   // Storage for plugin function errors
       
  1421   Popcorn.plugin.errors = [];
       
  1422 
       
  1423   // Returns wrapped plugin function
       
  1424   function safeTry( fn, pluginName ) {
       
  1425     return function() {
       
  1426 
       
  1427       //  When Popcorn.plugin.debug is true, do not suppress errors
       
  1428       if ( Popcorn.plugin.debug ) {
       
  1429         return fn.apply( this, arguments );
       
  1430       }
       
  1431 
       
  1432       try {
       
  1433         return fn.apply( this, arguments );
       
  1434       } catch ( ex ) {
       
  1435 
       
  1436         // Push plugin function errors into logging queue
       
  1437         Popcorn.plugin.errors.push({
       
  1438           plugin: pluginName,
       
  1439           thrown: ex,
       
  1440           source: fn.toString()
       
  1441         });
       
  1442 
       
  1443         // Trigger an error that the instance can listen for
       
  1444         // and react to
       
  1445         this.trigger( "error", Popcorn.plugin.errors );
       
  1446       }
       
  1447     };
       
  1448   }
       
  1449 
       
  1450   // Debug-mode flag for plugin development
       
  1451   Popcorn.plugin.debug = false;
       
  1452 
       
  1453   //  removePlugin( type ) removes all tracks of that from all instances of popcorn
       
  1454   //  removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
       
  1455   Popcorn.removePlugin = function( obj, name ) {
       
  1456 
       
  1457     //  Check if we are removing plugin from an instance or from all of Popcorn
       
  1458     if ( !name ) {
       
  1459 
       
  1460       //  Fix the order
       
  1461       name = obj;
       
  1462       obj = Popcorn.p;
       
  1463 
       
  1464       if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  1465         Popcorn.error( "'" + name + "' is a protected function name" );
       
  1466         return;
       
  1467       }
       
  1468 
       
  1469       var registryLen = Popcorn.registry.length,
       
  1470           registryIdx;
       
  1471 
       
  1472       // remove plugin reference from registry
       
  1473       for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
       
  1474         if ( Popcorn.registry[ registryIdx ].name === name ) {
       
  1475           Popcorn.registry.splice( registryIdx, 1 );
       
  1476           delete Popcorn.registryByName[ name ];
       
  1477           delete Popcorn.manifest[ name ];
       
  1478 
       
  1479           // delete the plugin
       
  1480           delete obj[ name ];
       
  1481 
       
  1482           // plugin found and removed, stop checking, we are done
       
  1483           return;
       
  1484         }
       
  1485       }
       
  1486 
       
  1487     }
       
  1488 
       
  1489     var byStart = obj.data.trackEvents.byStart,
       
  1490         byEnd = obj.data.trackEvents.byEnd,
       
  1491         animating = obj.data.trackEvents.animating,
       
  1492         idx, sl;
       
  1493 
       
  1494     // remove all trackEvents
       
  1495     for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
       
  1496 
       
  1497       if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) &&
       
  1498                 ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) {
       
  1499 
       
  1500         byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
       
  1501 
       
  1502         byStart.splice( idx, 1 );
       
  1503         byEnd.splice( idx, 1 );
       
  1504 
       
  1505         // update for loop if something removed, but keep checking
       
  1506         idx--; sl--;
       
  1507         if ( obj.data.trackEvents.startIndex <= idx ) {
       
  1508           obj.data.trackEvents.startIndex--;
       
  1509           obj.data.trackEvents.endIndex--;
       
  1510         }
       
  1511       }
       
  1512     }
       
  1513 
       
  1514     //remove all animating events
       
  1515     for ( idx = 0, sl = animating.length; idx < sl; idx++ ) {
       
  1516 
       
  1517       if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) {
       
  1518 
       
  1519         animating.splice( idx, 1 );
       
  1520 
       
  1521         // update for loop if something removed, but keep checking
       
  1522         idx--; sl--;
       
  1523       }
       
  1524     }
       
  1525 
       
  1526   };
       
  1527 
       
  1528   Popcorn.compositions = {};
       
  1529 
       
  1530   //  Plugin inheritance
       
  1531   Popcorn.compose = function( name, definition, manifest ) {
       
  1532 
       
  1533     //  If `manifest` arg is undefined, check for manifest within the `definition` object
       
  1534     //  If no `definition.manifest`, an empty object is a sufficient fallback
       
  1535     Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
       
  1536 
       
  1537     // register the effect by name
       
  1538     Popcorn.compositions[ name ] = definition;
       
  1539   };
       
  1540 
       
  1541   Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
       
  1542 
       
  1543   // stores parsers keyed on filetype
       
  1544   Popcorn.parsers = {};
       
  1545 
       
  1546   // An interface for extending Popcorn
       
  1547   // with parser functionality
       
  1548   Popcorn.parser = function( name, type, definition ) {
       
  1549 
       
  1550     if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  1551       Popcorn.error( "'" + name + "' is a protected function name" );
       
  1552       return;
       
  1553     }
       
  1554 
       
  1555     // fixes parameters for overloaded function call
       
  1556     if ( typeof type === "function" && !definition ) {
       
  1557       definition = type;
       
  1558       type = "";
       
  1559     }
       
  1560 
       
  1561     if ( typeof definition !== "function" || typeof type !== "string" ) {
       
  1562       return;
       
  1563     }
       
  1564 
       
  1565     // Provides some sugar, but ultimately extends
       
  1566     // the definition into Popcorn.p
       
  1567 
       
  1568     var natives = Popcorn.events.all,
       
  1569         parseFn,
       
  1570         parser = {};
       
  1571 
       
  1572     parseFn = function( filename, callback ) {
       
  1573 
       
  1574       if ( !filename ) {
       
  1575         return this;
       
  1576       }
       
  1577 
       
  1578       var that = this;
       
  1579 
       
  1580       Popcorn.xhr({
       
  1581         url: filename,
       
  1582         dataType: type,
       
  1583         success: function( data ) {
       
  1584 
       
  1585           var tracksObject = definition( data ),
       
  1586               tracksData,
       
  1587               tracksDataLen,
       
  1588               tracksDef,
       
  1589               idx = 0;
       
  1590 
       
  1591           tracksData = tracksObject.data || [];
       
  1592           tracksDataLen = tracksData.length;
       
  1593           tracksDef = null;
       
  1594 
       
  1595           //  If no tracks to process, return immediately
       
  1596           if ( !tracksDataLen ) {
       
  1597             return;
       
  1598           }
       
  1599 
       
  1600           //  Create tracks out of parsed object
       
  1601           for ( ; idx < tracksDataLen; idx++ ) {
       
  1602 
       
  1603             tracksDef = tracksData[ idx ];
       
  1604 
       
  1605             for ( var key in tracksDef ) {
       
  1606 
       
  1607               if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
       
  1608 
       
  1609                 that[ key ]( tracksDef[ key ] );
       
  1610               }
       
  1611             }
       
  1612           }
       
  1613           if ( callback ) {
       
  1614             callback();
       
  1615           }
       
  1616         }
       
  1617       });
       
  1618 
       
  1619       return this;
       
  1620     };
       
  1621 
       
  1622     // Assign new named definition
       
  1623     parser[ name ] = parseFn;
       
  1624 
       
  1625     // Extend Popcorn.p with new named definition
       
  1626     Popcorn.extend( Popcorn.p, parser );
       
  1627 
       
  1628     // keys the function name by filetype extension
       
  1629     //Popcorn.parsers[ name ] = true;
       
  1630 
       
  1631     return parser;
       
  1632   };
       
  1633 
       
  1634   Popcorn.player = function( name, player ) {
       
  1635 
       
  1636     player = player || {};
       
  1637 
       
  1638     var playerFn = function( target, src, options ) {
       
  1639 
       
  1640       options = options || {};
       
  1641 
       
  1642       // List of events
       
  1643       var date = new Date() / 1000,
       
  1644           baselineTime = date,
       
  1645           currentTime = 0,
       
  1646           volume = 1,
       
  1647           muted = false,
       
  1648           events = {},
       
  1649 
       
  1650           // The container div of the resource
       
  1651           container = document.getElementById( rIdExp.exec( target ) && rIdExp.exec( target )[ 2 ] ) ||
       
  1652                         document.getElementById( target ) ||
       
  1653                           target,
       
  1654           basePlayer = {},
       
  1655           timeout,
       
  1656           popcorn;
       
  1657 
       
  1658       // copies a div into the media object
       
  1659       for( var val in container ) {
       
  1660 
       
  1661         if ( typeof container[ val ] === "object" ) {
       
  1662 
       
  1663           basePlayer[ val ] = container[ val ];
       
  1664         } else if ( typeof container[ val ] === "function" ) {
       
  1665 
       
  1666           basePlayer[ val ] = (function( value ) {
       
  1667 
       
  1668             // this is a stupid ugly kludgy hack in honour of Safari
       
  1669             // in Safari a NodeList is a function, not an object
       
  1670             if ( "length" in container[ value ] && !container[ value ].call ) {
       
  1671 
       
  1672               return container[ value ];
       
  1673             } else {
       
  1674 
       
  1675               return function() {
       
  1676 
       
  1677                 return container[ value ].apply( container, arguments );
       
  1678               };
       
  1679             }
       
  1680           }( val ));
       
  1681         } else {
       
  1682 
       
  1683           Popcorn.player.defineProperty( basePlayer, val, {
       
  1684             get: (function( value ) {
       
  1685 
       
  1686               return function() {
       
  1687 
       
  1688                 return container[ value ];
       
  1689               };
       
  1690             }( val )),
       
  1691             set: Popcorn.nop,
       
  1692             configurable: true
       
  1693           });
       
  1694         }
       
  1695       }
       
  1696 
       
  1697       var timeupdate = function() {
       
  1698 
       
  1699         date = new Date() / 1000;
       
  1700 
       
  1701         if ( !basePlayer.paused ) {
       
  1702 
       
  1703           basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime );
       
  1704           basePlayer.dispatchEvent( "timeupdate" );
       
  1705           timeout = setTimeout( timeupdate, 10 );
       
  1706         }
       
  1707 
       
  1708         baselineTime = date;
       
  1709       };
       
  1710 
       
  1711       basePlayer.play = function() {
       
  1712 
       
  1713         this.paused = false;
       
  1714 
       
  1715         if ( basePlayer.readyState >= 4 ) {
       
  1716 
       
  1717           baselineTime = new Date() / 1000;
       
  1718           basePlayer.dispatchEvent( "play" );
       
  1719           timeupdate();
       
  1720         }
       
  1721       };
       
  1722 
       
  1723       basePlayer.pause = function() {
       
  1724 
       
  1725         this.paused = true;
       
  1726         basePlayer.dispatchEvent( "pause" );
       
  1727       };
       
  1728 
       
  1729       Popcorn.player.defineProperty( basePlayer, "currentTime", {
       
  1730         get: function() {
       
  1731 
       
  1732           return currentTime;
       
  1733         },
       
  1734         set: function( val ) {
       
  1735 
       
  1736           // make sure val is a number
       
  1737           currentTime = +val;
       
  1738           basePlayer.dispatchEvent( "timeupdate" );
       
  1739           return currentTime;
       
  1740         },
       
  1741         configurable: true
       
  1742       });
       
  1743 
       
  1744       Popcorn.player.defineProperty( basePlayer, "volume", {
       
  1745         get: function() {
       
  1746 
       
  1747           return volume;
       
  1748         },
       
  1749         set: function( val ) {
       
  1750 
       
  1751           // make sure val is a number
       
  1752           volume = +val;
       
  1753           basePlayer.dispatchEvent( "volumechange" );
       
  1754           return volume;
       
  1755         },
       
  1756         configurable: true
       
  1757       });
       
  1758 
       
  1759       Popcorn.player.defineProperty( basePlayer, "muted", {
       
  1760         get: function() {
       
  1761 
       
  1762           return muted;
       
  1763         },
       
  1764         set: function( val ) {
       
  1765 
       
  1766           // make sure val is a number
       
  1767           muted = +val;
       
  1768           basePlayer.dispatchEvent( "volumechange" );
       
  1769           return muted;
       
  1770         },
       
  1771         configurable: true
       
  1772       });
       
  1773 
       
  1774       // Adds an event listener to the object
       
  1775       basePlayer.addEventListener = function( evtName, fn ) {
       
  1776 
       
  1777         if ( !events[ evtName ] ) {
       
  1778 
       
  1779           events[ evtName ] = [];
       
  1780         }
       
  1781 
       
  1782         events[ evtName ].push( fn );
       
  1783         return fn;
       
  1784       };
       
  1785 
       
  1786       // Can take event object or simple string
       
  1787       basePlayer.dispatchEvent = function( oEvent ) {
       
  1788 
       
  1789         var evt,
       
  1790             self = this,
       
  1791             eventInterface,
       
  1792             eventName = oEvent.type;
       
  1793 
       
  1794         // A string was passed, create event object
       
  1795         if ( !eventName ) {
       
  1796 
       
  1797           eventName = oEvent;
       
  1798           eventInterface  = Popcorn.events.getInterface( eventName );
       
  1799 
       
  1800           if ( eventInterface ) {
       
  1801 
       
  1802             evt = document.createEvent( eventInterface );
       
  1803             evt.initEvent( eventName, true, true, window, 1 );
       
  1804           }
       
  1805         }
       
  1806 
       
  1807         Popcorn.forEach( events[ eventName ], function( val ) {
       
  1808 
       
  1809           val.call( self, evt, self );
       
  1810         });
       
  1811       };
       
  1812 
       
  1813       // Attempt to get src from playerFn parameter
       
  1814       basePlayer.src = src || "";
       
  1815       basePlayer.readyState = 0;
       
  1816       basePlayer.duration = 0;
       
  1817       basePlayer.paused = true;
       
  1818       basePlayer.ended = 0;
       
  1819 
       
  1820       if ( player._setup ) {
       
  1821 
       
  1822         player._setup.call( basePlayer, options );
       
  1823       } else {
       
  1824 
       
  1825         // there is no setup, which means there is nothing to load
       
  1826         basePlayer.readyState = 4;
       
  1827         basePlayer.dispatchEvent( "load" );
       
  1828         basePlayer.dispatchEvent( "loadeddata" );
       
  1829       }
       
  1830 
       
  1831       // when a custom player is loaded, load basePlayer state into custom player
       
  1832       basePlayer.addEventListener( "load", function() {
       
  1833 
       
  1834         // if a player is not ready before currentTime is called, this will set it after it is ready
       
  1835         basePlayer.currentTime = currentTime;
       
  1836 
       
  1837         // same as above with volume and muted
       
  1838         basePlayer.volume = volume;
       
  1839         basePlayer.muted = muted;
       
  1840       });
       
  1841 
       
  1842       basePlayer.addEventListener( "loadeddata", function() {
       
  1843 
       
  1844         // if play was called before player ready, start playing video
       
  1845         !basePlayer.paused && basePlayer.play();
       
  1846       });
       
  1847 
       
  1848       popcorn = new Popcorn.p.init( basePlayer, options );
       
  1849 
       
  1850       return popcorn;
       
  1851     };
       
  1852 
       
  1853     Popcorn[ name ] = Popcorn[ name ] || playerFn;
       
  1854   };
       
  1855 
       
  1856   Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
       
  1857 
       
  1858     object.__defineGetter__( description, options.get || Popcorn.nop );
       
  1859     object.__defineSetter__( description, options.set || Popcorn.nop );
       
  1860   };
       
  1861 
       
  1862   //  Cache references to reused RegExps
       
  1863   var rparams = /\?/,
       
  1864   //  XHR Setup object
       
  1865   setup = {
       
  1866     url: "",
       
  1867     data: "",
       
  1868     dataType: "",
       
  1869     success: Popcorn.nop,
       
  1870     type: "GET",
       
  1871     async: true,
       
  1872     xhr: function() {
       
  1873       return new global.XMLHttpRequest();
       
  1874     }
       
  1875   };
       
  1876 
       
  1877   Popcorn.xhr = function( options ) {
       
  1878 
       
  1879     options.dataType = options.dataType && options.dataType.toLowerCase() || null;
       
  1880 
       
  1881     if ( options.dataType &&
       
  1882          ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
       
  1883 
       
  1884       Popcorn.xhr.getJSONP(
       
  1885         options.url,
       
  1886         options.success,
       
  1887         options.dataType === "script"
       
  1888       );
       
  1889       return;
       
  1890     }
       
  1891 
       
  1892     var settings = Popcorn.extend( {}, setup, options );
       
  1893 
       
  1894     //  Create new XMLHttpRequest object
       
  1895     settings.ajax  = settings.xhr();
       
  1896 
       
  1897     if ( settings.ajax ) {
       
  1898 
       
  1899       if ( settings.type === "GET" && settings.data ) {
       
  1900 
       
  1901         //  append query string
       
  1902         settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
       
  1903 
       
  1904         //  Garbage collect and reset settings.data
       
  1905         settings.data = null;
       
  1906       }
       
  1907 
       
  1908 
       
  1909       settings.ajax.open( settings.type, settings.url, settings.async );
       
  1910       settings.ajax.send( settings.data || null );
       
  1911 
       
  1912       return Popcorn.xhr.httpData( settings );
       
  1913     }
       
  1914   };
       
  1915 
       
  1916 
       
  1917   Popcorn.xhr.httpData = function( settings ) {
       
  1918 
       
  1919     var data, json = null,
       
  1920         parser, xml = null;
       
  1921 
       
  1922     settings.ajax.onreadystatechange = function() {
       
  1923 
       
  1924       if ( settings.ajax.readyState === 4 ) {
       
  1925 
       
  1926         try {
       
  1927           json = JSON.parse( settings.ajax.responseText );
       
  1928         } catch( e ) {
       
  1929           //suppress
       
  1930         }
       
  1931 
       
  1932         data = {
       
  1933           xml: settings.ajax.responseXML,
       
  1934           text: settings.ajax.responseText,
       
  1935           json: json
       
  1936         };
       
  1937 
       
  1938         // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml
       
  1939         if ( !data.xml || !data.xml.documentElement ) {
       
  1940           data.xml = null;
       
  1941 
       
  1942           try {
       
  1943             parser = new DOMParser();
       
  1944             xml = parser.parseFromString( settings.ajax.responseText, "text/xml" );
       
  1945 
       
  1946             if ( !xml.getElementsByTagName( "parsererror" ).length ) {
       
  1947               data.xml = xml;
       
  1948             }
       
  1949           } catch ( e ) {
       
  1950             // data.xml remains null
       
  1951           }
       
  1952         }
       
  1953 
       
  1954         //  If a dataType was specified, return that type of data
       
  1955         if ( settings.dataType ) {
       
  1956           data = data[ settings.dataType ];
       
  1957         }
       
  1958 
       
  1959 
       
  1960         settings.success.call( settings.ajax, data );
       
  1961 
       
  1962       }
       
  1963     };
       
  1964     return data;
       
  1965   };
       
  1966 
       
  1967   Popcorn.xhr.getJSONP = function( url, success, isScript ) {
       
  1968 
       
  1969     var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
       
  1970       script = document.createElement( "script" ),
       
  1971       paramStr = url.split( "?" )[ 1 ],
       
  1972       isFired = false,
       
  1973       params = [],
       
  1974       callback, parts, callparam;
       
  1975 
       
  1976     if ( paramStr && !isScript ) {
       
  1977       params = paramStr.split( "&" );
       
  1978     }
       
  1979 
       
  1980     if ( params.length ) {
       
  1981       parts = params[ params.length - 1 ].split( "=" );
       
  1982     }
       
  1983 
       
  1984     callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ]  ) : "jsonp";
       
  1985 
       
  1986     if ( !paramStr && !isScript ) {
       
  1987       url += "?callback=" + callback;
       
  1988     }
       
  1989 
       
  1990     if ( callback && !isScript ) {
       
  1991 
       
  1992       //  If a callback name already exists
       
  1993       if ( !!window[ callback ] ) {
       
  1994         //  Create a new unique callback name
       
  1995         callback = Popcorn.guid( callback );
       
  1996       }
       
  1997 
       
  1998       //  Define the JSONP success callback globally
       
  1999       window[ callback ] = function( data ) {
       
  2000         // Fire success callbacks
       
  2001         success && success( data );
       
  2002         isFired = true;
       
  2003       };
       
  2004 
       
  2005       //  Replace callback param and callback name
       
  2006       url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback );
       
  2007     }
       
  2008 
       
  2009     script.onload = function() {
       
  2010 
       
  2011       //  Handling remote script loading callbacks
       
  2012       if ( isScript ) {
       
  2013         //  getScript
       
  2014         success && success();
       
  2015       }
       
  2016 
       
  2017       //  Executing for JSONP requests
       
  2018       if ( isFired ) {
       
  2019         //  Garbage collect the callback
       
  2020         delete window[ callback ];
       
  2021       }
       
  2022       //  Garbage collect the script resource
       
  2023       head.removeChild( script );
       
  2024     };
       
  2025 
       
  2026     script.src = url;
       
  2027 
       
  2028     head.insertBefore( script, head.firstChild );
       
  2029 
       
  2030     return;
       
  2031   };
       
  2032 
       
  2033   Popcorn.getJSONP = Popcorn.xhr.getJSONP;
       
  2034 
       
  2035   Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
       
  2036 
       
  2037     return Popcorn.xhr.getJSONP( url, success, true );
       
  2038   };
       
  2039 
       
  2040   Popcorn.util = {
       
  2041     // Simple function to parse a timestamp into seconds
       
  2042     // Acceptable formats are:
       
  2043     // HH:MM:SS.MMM
       
  2044     // HH:MM:SS;FF
       
  2045     // Hours and minutes are optional. They default to 0
       
  2046     toSeconds: function( timeStr, framerate ) {
       
  2047       // Hours and minutes are optional
       
  2048       // Seconds must be specified
       
  2049       // Seconds can be followed by milliseconds OR by the frame information
       
  2050       var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
       
  2051           errorMessage = "Invalid time format",
       
  2052           digitPairs, lastIndex, lastPair, firstPair,
       
  2053           frameInfo, frameTime;
       
  2054 
       
  2055       if ( typeof timeStr === "number" ) {
       
  2056         return timeStr;
       
  2057       }
       
  2058 
       
  2059       if ( typeof timeStr === "string" &&
       
  2060             !validTimeFormat.test( timeStr ) ) {
       
  2061         Popcorn.error( errorMessage );
       
  2062       }
       
  2063 
       
  2064       digitPairs = timeStr.split( ":" );
       
  2065       lastIndex = digitPairs.length - 1;
       
  2066       lastPair = digitPairs[ lastIndex ];
       
  2067 
       
  2068       // Fix last element:
       
  2069       if ( lastPair.indexOf( ";" ) > -1 ) {
       
  2070 
       
  2071         frameInfo = lastPair.split( ";" );
       
  2072         frameTime = 0;
       
  2073 
       
  2074         if ( framerate && ( typeof framerate === "number" ) ) {
       
  2075           frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
       
  2076         }
       
  2077 
       
  2078         digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime;
       
  2079       }
       
  2080 
       
  2081       firstPair = digitPairs[ 0 ];
       
  2082 
       
  2083       return {
       
  2084 
       
  2085         1: parseFloat( firstPair, 10 ),
       
  2086 
       
  2087         2: ( parseInt( firstPair, 10 ) * 60 ) +
       
  2088               parseFloat( digitPairs[ 1 ], 10 ),
       
  2089 
       
  2090         3: ( parseInt( firstPair, 10 ) * 3600 ) +
       
  2091             ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) +
       
  2092               parseFloat( digitPairs[ 2 ], 10 )
       
  2093 
       
  2094       }[ digitPairs.length || 1 ];
       
  2095     }
       
  2096   };
       
  2097 
       
  2098 
       
  2099   // Initialize locale data
       
  2100   // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes
       
  2101   function initLocale( arg ) {
       
  2102 
       
  2103     var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ),
       
  2104         parts = locale.split( "-" );
       
  2105 
       
  2106     // Setup locale data table
       
  2107     return {
       
  2108       iso6391: locale,
       
  2109       language: parts[ 0 ] || "",
       
  2110       region: parts[ 1 ] || ""
       
  2111     };
       
  2112   }
       
  2113 
       
  2114   // Declare locale data table
       
  2115   var localeData = initLocale( global.navigator.userLanguage || global.navigator.language );
       
  2116 
       
  2117   Popcorn.locale = {
       
  2118 
       
  2119     // Popcorn.locale.get()
       
  2120     // returns reference to privately
       
  2121     // defined localeData
       
  2122     get: function() {
       
  2123       return localeData;
       
  2124     },
       
  2125 
       
  2126     // Popcorn.locale.set( string|object );
       
  2127     set: function( arg ) {
       
  2128 
       
  2129       localeData = initLocale( arg );
       
  2130 
       
  2131       Popcorn.locale.broadcast();
       
  2132 
       
  2133       return localeData;
       
  2134     },
       
  2135 
       
  2136     // Popcorn.locale.broadcast( type )
       
  2137     // Sends events to all popcorn media instances that are
       
  2138     // listening for locale events
       
  2139     broadcast: function( type ) {
       
  2140 
       
  2141       var instances = Popcorn.instances,
       
  2142           length = instances.length,
       
  2143           idx = 0,
       
  2144           instance;
       
  2145 
       
  2146       type = type || "locale:changed";
       
  2147 
       
  2148       // Iterate all current instances
       
  2149       for ( ; idx < length; idx++ ) {
       
  2150         instance = instances[ idx ];
       
  2151 
       
  2152         // For those instances with locale event listeners,
       
  2153         // trigger a locale change event
       
  2154         if ( type in instance.data.events  ) {
       
  2155           instance.trigger( type );
       
  2156         }
       
  2157       }
       
  2158     }
       
  2159   };
       
  2160 
       
  2161   // alias for exec function
       
  2162   Popcorn.p.cue = Popcorn.p.exec;
       
  2163 
       
  2164   function getItems() {
       
  2165 
       
  2166     var item,
       
  2167         list = [];
       
  2168 
       
  2169     if ( Object.keys ) {
       
  2170       list = Object.keys( Popcorn.p );
       
  2171     } else {
       
  2172 
       
  2173       for ( item in Popcorn.p ) {
       
  2174         if ( hasOwn.call( Popcorn.p, item ) ) {
       
  2175           list.push( item );
       
  2176         }
       
  2177       }
       
  2178     }
       
  2179 
       
  2180     return list.join( "," ).toLowerCase().split( ",");
       
  2181   }
       
  2182 
       
  2183   //  Protected API methods
       
  2184   Popcorn.protect = {
       
  2185     natives: getItems()
       
  2186   };
       
  2187 
       
  2188   //  Exposes Popcorn to global context
       
  2189   global.Popcorn = Popcorn;
       
  2190 
       
  2191 })(window, window.document);