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