web/static/res/js/popcorn-complete.js
changeset 18 f6232b308fbd
child 36 6cd5bc3dc7a2
equal deleted inserted replaced
17:ec4f33084f8d 18:f6232b308fbd
       
     1 /*
       
     2  * popcorn.js version 1.3
       
     3  * http://popcornjs.org
       
     4  *
       
     5  * Copyright 2011, Mozilla Foundation
       
     6  * Licensed under the MIT license
       
     7  */
       
     8 
       
     9 (function(global, document) {
       
    10 
       
    11   // Popcorn.js does not support archaic browsers
       
    12   if ( !document.addEventListener ) {
       
    13     global.Popcorn = {
       
    14       isSupported: false
       
    15     };
       
    16 
       
    17     var methods = ( "byId forEach extend effects error guid sizeOf isArray nop position disable enable destroy" +
       
    18           "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
       
    19           "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/);
       
    20 
       
    21     while ( methods.length ) {
       
    22       global.Popcorn[ methods.shift() ] = function() {};
       
    23     }
       
    24     return;
       
    25   }
       
    26 
       
    27   var
       
    28 
       
    29   AP = Array.prototype,
       
    30   OP = Object.prototype,
       
    31 
       
    32   forEach = AP.forEach,
       
    33   slice = AP.slice,
       
    34   hasOwn = OP.hasOwnProperty,
       
    35   toString = OP.toString,
       
    36 
       
    37   // Copy global Popcorn (may not exist)
       
    38   _Popcorn = global.Popcorn,
       
    39 
       
    40   //  Ready fn cache
       
    41   readyStack = [],
       
    42   readyBound = false,
       
    43   readyFired = false,
       
    44 
       
    45   //  Non-public internal data object
       
    46   internal = {
       
    47     events: {
       
    48       hash: {},
       
    49       apis: {}
       
    50     }
       
    51   },
       
    52 
       
    53   //  Non-public `requestAnimFrame`
       
    54   //  http://paulirish.com/2011/requestanimationframe-for-smart-animating/
       
    55   requestAnimFrame = (function(){
       
    56     return global.requestAnimationFrame ||
       
    57       global.webkitRequestAnimationFrame ||
       
    58       global.mozRequestAnimationFrame ||
       
    59       global.oRequestAnimationFrame ||
       
    60       global.msRequestAnimationFrame ||
       
    61       function( callback, element ) {
       
    62         global.setTimeout( callback, 16 );
       
    63       };
       
    64   }()),
       
    65 
       
    66   //  Non-public `getKeys`, return an object's keys as an array
       
    67   getKeys = function( obj ) {
       
    68     return Object.keys ? Object.keys( obj ) : (function( obj ) {
       
    69       var item,
       
    70           list = [];
       
    71 
       
    72       for ( item in obj ) {
       
    73         if ( hasOwn.call( obj, item ) ) {
       
    74           list.push( item );
       
    75         }
       
    76       }
       
    77       return list;
       
    78     })( obj );
       
    79   },
       
    80 
       
    81   //  Declare constructor
       
    82   //  Returns an instance object.
       
    83   Popcorn = function( entity, options ) {
       
    84     //  Return new Popcorn object
       
    85     return new Popcorn.p.init( entity, options || null );
       
    86   };
       
    87 
       
    88   //  Popcorn API version, automatically inserted via build system.
       
    89   Popcorn.version = "1.3";
       
    90 
       
    91   //  Boolean flag allowing a client to determine if Popcorn can be supported
       
    92   Popcorn.isSupported = true;
       
    93 
       
    94   //  Instance caching
       
    95   Popcorn.instances = [];
       
    96 
       
    97   //  Declare a shortcut (Popcorn.p) to and a definition of
       
    98   //  the new prototype for our Popcorn constructor
       
    99   Popcorn.p = Popcorn.prototype = {
       
   100 
       
   101     init: function( entity, options ) {
       
   102 
       
   103       var matches, nodeName,
       
   104           self = this;
       
   105 
       
   106       //  Supports Popcorn(function () { /../ })
       
   107       //  Originally proposed by Daniel Brooks
       
   108 
       
   109       if ( typeof entity === "function" ) {
       
   110 
       
   111         //  If document ready has already fired
       
   112         if ( document.readyState === "complete" ) {
       
   113 
       
   114           entity( document, Popcorn );
       
   115 
       
   116           return;
       
   117         }
       
   118         //  Add `entity` fn to ready stack
       
   119         readyStack.push( entity );
       
   120 
       
   121         //  This process should happen once per page load
       
   122         if ( !readyBound ) {
       
   123 
       
   124           //  set readyBound flag
       
   125           readyBound = true;
       
   126 
       
   127           var DOMContentLoaded  = function() {
       
   128 
       
   129             readyFired = true;
       
   130 
       
   131             //  Remove global DOM ready listener
       
   132             document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
       
   133 
       
   134             //  Execute all ready function in the stack
       
   135             for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
       
   136 
       
   137               readyStack[ i ].call( document, Popcorn );
       
   138 
       
   139             }
       
   140             //  GC readyStack
       
   141             readyStack = null;
       
   142           };
       
   143 
       
   144           //  Register global DOM ready listener
       
   145           document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
       
   146         }
       
   147 
       
   148         return;
       
   149       }
       
   150 
       
   151       if ( typeof entity === "string" ) {
       
   152         try {
       
   153           matches = document.querySelector( entity );
       
   154         } catch( e ) {
       
   155           throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity );
       
   156         }
       
   157       }
       
   158 
       
   159       //  Get media element by id or object reference
       
   160       this.media = matches || entity;
       
   161 
       
   162       //  inner reference to this media element's nodeName string value
       
   163       nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video";
       
   164 
       
   165       //  Create an audio or video element property reference
       
   166       this[ nodeName ] = this.media;
       
   167 
       
   168       this.options = options || {};
       
   169 
       
   170       //  Resolve custom ID or default prefixed ID
       
   171       this.id = this.options.id || Popcorn.guid( nodeName );
       
   172 
       
   173       //  Throw if an attempt is made to use an ID that already exists
       
   174       if ( Popcorn.byId( this.id ) ) {
       
   175         throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" );
       
   176       }
       
   177 
       
   178       this.isDestroyed = false;
       
   179 
       
   180       this.data = {
       
   181 
       
   182         // data structure of all
       
   183         running: {
       
   184           cue: []
       
   185         },
       
   186 
       
   187         // Executed by either timeupdate event or in rAF loop
       
   188         timeUpdate: Popcorn.nop,
       
   189 
       
   190         // Allows disabling a plugin per instance
       
   191         disabled: {},
       
   192 
       
   193         // Stores DOM event queues by type
       
   194         events: {},
       
   195 
       
   196         // Stores Special event hooks data
       
   197         hooks: {},
       
   198 
       
   199         // Store track event history data
       
   200         history: [],
       
   201 
       
   202         // Stores ad-hoc state related data]
       
   203         state: {
       
   204           volume: this.media.volume
       
   205         },
       
   206 
       
   207         // Store track event object references by trackId
       
   208         trackRefs: {},
       
   209 
       
   210         // Playback track event queues
       
   211         trackEvents: {
       
   212           byStart: [{
       
   213 
       
   214             start: -1,
       
   215             end: -1
       
   216           }],
       
   217           byEnd: [{
       
   218             start: -1,
       
   219             end: -1
       
   220           }],
       
   221           animating: [],
       
   222           startIndex: 0,
       
   223           endIndex: 0,
       
   224           previousUpdateTime: -1
       
   225         }
       
   226       };
       
   227 
       
   228       //  Register new instance
       
   229       Popcorn.instances.push( this );
       
   230 
       
   231       //  function to fire when video is ready
       
   232       var isReady = function() {
       
   233 
       
   234         // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598
       
   235         // it is possible the video's time is less than 0
       
   236         // this has the potential to call track events more than once, when they should not
       
   237         // start: 0, end: 1 will start, end, start again, when it should just start
       
   238         // just setting it to 0 if it is below 0 fixes this issue
       
   239         if ( self.media.currentTime < 0 ) {
       
   240 
       
   241           self.media.currentTime = 0;
       
   242         }
       
   243 
       
   244         self.media.removeEventListener( "loadeddata", isReady, false );
       
   245 
       
   246         var duration, videoDurationPlus,
       
   247             runningPlugins, runningPlugin, rpLength, rpNatives;
       
   248 
       
   249         //  Adding padding to the front and end of the arrays
       
   250         //  this is so we do not fall off either end
       
   251         duration = self.media.duration;
       
   252 
       
   253         //  Check for no duration info (NaN)
       
   254         videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
       
   255 
       
   256         Popcorn.addTrackEvent( self, {
       
   257           start: videoDurationPlus,
       
   258           end: videoDurationPlus
       
   259         });
       
   260 
       
   261         if ( self.options.frameAnimation ) {
       
   262 
       
   263           //  if Popcorn is created with frameAnimation option set to true,
       
   264           //  requestAnimFrame is used instead of "timeupdate" media event.
       
   265           //  This is for greater frame time accuracy, theoretically up to
       
   266           //  60 frames per second as opposed to ~4 ( ~every 15-250ms)
       
   267           self.data.timeUpdate = function () {
       
   268 
       
   269             Popcorn.timeUpdate( self, {} );
       
   270 
       
   271             // fire frame for each enabled active plugin of every type
       
   272             Popcorn.forEach( Popcorn.manifest, function( key, val ) {
       
   273 
       
   274               runningPlugins = self.data.running[ val ];
       
   275 
       
   276               // ensure there are running plugins on this type on this instance
       
   277               if ( runningPlugins ) {
       
   278 
       
   279                 rpLength = runningPlugins.length;
       
   280                 for ( var i = 0; i < rpLength; i++ ) {
       
   281 
       
   282                   runningPlugin = runningPlugins[ i ];
       
   283                   rpNatives = runningPlugin._natives;
       
   284                   rpNatives && rpNatives.frame &&
       
   285                     rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() );
       
   286                 }
       
   287               }
       
   288             });
       
   289 
       
   290             self.emit( "timeupdate" );
       
   291 
       
   292             !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
       
   293           };
       
   294 
       
   295           !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
       
   296 
       
   297         } else {
       
   298 
       
   299           self.data.timeUpdate = function( event ) {
       
   300             Popcorn.timeUpdate( self, event );
       
   301           };
       
   302 
       
   303           if ( !self.isDestroyed ) {
       
   304             self.media.addEventListener( "timeupdate", self.data.timeUpdate, false );
       
   305           }
       
   306         }
       
   307       };
       
   308 
       
   309       Object.defineProperty( this, "error", {
       
   310         get: function() {
       
   311 
       
   312           return self.media.error;
       
   313         }
       
   314       });
       
   315 
       
   316       if ( self.media.readyState >= 2 ) {
       
   317 
       
   318         isReady();
       
   319       } else {
       
   320 
       
   321         self.media.addEventListener( "loadeddata", isReady, false );
       
   322       }
       
   323 
       
   324       return this;
       
   325     }
       
   326   };
       
   327 
       
   328   //  Extend constructor prototype to instance prototype
       
   329   //  Allows chaining methods to instances
       
   330   Popcorn.p.init.prototype = Popcorn.p;
       
   331 
       
   332   Popcorn.byId = function( str ) {
       
   333     var instances = Popcorn.instances,
       
   334         length = instances.length,
       
   335         i = 0;
       
   336 
       
   337     for ( ; i < length; i++ ) {
       
   338       if ( instances[ i ].id === str ) {
       
   339         return instances[ i ];
       
   340       }
       
   341     }
       
   342 
       
   343     return null;
       
   344   };
       
   345 
       
   346   Popcorn.forEach = function( obj, fn, context ) {
       
   347 
       
   348     if ( !obj || !fn ) {
       
   349       return {};
       
   350     }
       
   351 
       
   352     context = context || this;
       
   353 
       
   354     var key, len;
       
   355 
       
   356     // Use native whenever possible
       
   357     if ( forEach && obj.forEach === forEach ) {
       
   358       return obj.forEach( fn, context );
       
   359     }
       
   360 
       
   361     if ( toString.call( obj ) === "[object NodeList]" ) {
       
   362       for ( key = 0, len = obj.length; key < len; key++ ) {
       
   363         fn.call( context, obj[ key ], key, obj );
       
   364       }
       
   365       return obj;
       
   366     }
       
   367 
       
   368     for ( key in obj ) {
       
   369       if ( hasOwn.call( obj, key ) ) {
       
   370         fn.call( context, obj[ key ], key, obj );
       
   371       }
       
   372     }
       
   373     return obj;
       
   374   };
       
   375 
       
   376   Popcorn.extend = function( obj ) {
       
   377     var dest = obj, src = slice.call( arguments, 1 );
       
   378 
       
   379     Popcorn.forEach( src, function( copy ) {
       
   380       for ( var prop in copy ) {
       
   381         dest[ prop ] = copy[ prop ];
       
   382       }
       
   383     });
       
   384 
       
   385     return dest;
       
   386   };
       
   387 
       
   388 
       
   389   // A Few reusable utils, memoized onto Popcorn
       
   390   Popcorn.extend( Popcorn, {
       
   391     noConflict: function( deep ) {
       
   392 
       
   393       if ( deep ) {
       
   394         global.Popcorn = _Popcorn;
       
   395       }
       
   396 
       
   397       return Popcorn;
       
   398     },
       
   399     error: function( msg ) {
       
   400       throw new Error( msg );
       
   401     },
       
   402     guid: function( prefix ) {
       
   403       Popcorn.guid.counter++;
       
   404       return  ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
       
   405     },
       
   406     sizeOf: function( obj ) {
       
   407       var size = 0;
       
   408 
       
   409       for ( var prop in obj ) {
       
   410         size++;
       
   411       }
       
   412 
       
   413       return size;
       
   414     },
       
   415     isArray: Array.isArray || function( array ) {
       
   416       return toString.call( array ) === "[object Array]";
       
   417     },
       
   418 
       
   419     nop: function() {},
       
   420 
       
   421     position: function( elem ) {
       
   422 
       
   423       var clientRect = elem.getBoundingClientRect(),
       
   424           bounds = {},
       
   425           doc = elem.ownerDocument,
       
   426           docElem = document.documentElement,
       
   427           body = document.body,
       
   428           clientTop, clientLeft, scrollTop, scrollLeft, top, left;
       
   429 
       
   430       //  Determine correct clientTop/Left
       
   431       clientTop = docElem.clientTop || body.clientTop || 0;
       
   432       clientLeft = docElem.clientLeft || body.clientLeft || 0;
       
   433 
       
   434       //  Determine correct scrollTop/Left
       
   435       scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
       
   436       scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
       
   437 
       
   438       //  Temp top/left
       
   439       top = Math.ceil( clientRect.top + scrollTop - clientTop );
       
   440       left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
       
   441 
       
   442       for ( var p in clientRect ) {
       
   443         bounds[ p ] = Math.round( clientRect[ p ] );
       
   444       }
       
   445 
       
   446       return Popcorn.extend({}, bounds, { top: top, left: left });
       
   447     },
       
   448 
       
   449     disable: function( instance, plugin ) {
       
   450 
       
   451       if ( !instance.data.disabled[ plugin ] ) {
       
   452 
       
   453         instance.data.disabled[ plugin ] = true;
       
   454 
       
   455         for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
       
   456 
       
   457           event = instance.data.running[ plugin ][ i ];
       
   458           event._natives.end.call( instance, null, event  );
       
   459         }
       
   460       }
       
   461 
       
   462       return instance;
       
   463     },
       
   464     enable: function( instance, plugin ) {
       
   465 
       
   466       if ( instance.data.disabled[ plugin ] ) {
       
   467 
       
   468         instance.data.disabled[ plugin ] = false;
       
   469 
       
   470         for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
       
   471 
       
   472           event = instance.data.running[ plugin ][ i ];
       
   473           event._natives.start.call( instance, null, event  );
       
   474         }
       
   475       }
       
   476 
       
   477       return instance;
       
   478     },
       
   479     destroy: function( instance ) {
       
   480       var events = instance.data.events,
       
   481           trackEvents = instance.data.trackEvents,
       
   482           singleEvent, item, fn, plugin;
       
   483 
       
   484       //  Iterate through all events and remove them
       
   485       for ( item in events ) {
       
   486         singleEvent = events[ item ];
       
   487         for ( fn in singleEvent ) {
       
   488           delete singleEvent[ fn ];
       
   489         }
       
   490         events[ item ] = null;
       
   491       }
       
   492 
       
   493       // remove all plugins off the given instance
       
   494       for ( plugin in Popcorn.registryByName ) {
       
   495         Popcorn.removePlugin( instance, plugin );
       
   496       }
       
   497 
       
   498       // Remove all data.trackEvents #1178
       
   499       trackEvents.byStart.length = 0;
       
   500       trackEvents.byEnd.length = 0;
       
   501 
       
   502       if ( !instance.isDestroyed ) {
       
   503         instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false );
       
   504         instance.isDestroyed = true;
       
   505       }
       
   506     }
       
   507   });
       
   508 
       
   509   //  Memoized GUID Counter
       
   510   Popcorn.guid.counter = 1;
       
   511 
       
   512   //  Factory to implement getters, setters and controllers
       
   513   //  as Popcorn instance methods. The IIFE will create and return
       
   514   //  an object with defined methods
       
   515   Popcorn.extend(Popcorn.p, (function() {
       
   516 
       
   517       var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
       
   518                     "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
       
   519           ret = {};
       
   520 
       
   521 
       
   522       //  Build methods, store in object that is returned and passed to extend
       
   523       Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
       
   524 
       
   525         ret[ name ] = function( arg ) {
       
   526           var previous;
       
   527 
       
   528           if ( typeof this.media[ name ] === "function" ) {
       
   529 
       
   530             // Support for shorthanded play(n)/pause(n) jump to currentTime
       
   531             // If arg is not null or undefined and called by one of the
       
   532             // allowed shorthandable methods, then set the currentTime
       
   533             // Supports time as seconds or SMPTE
       
   534             if ( arg != null && /play|pause/.test( name ) ) {
       
   535               this.media.currentTime = Popcorn.util.toSeconds( arg );
       
   536             }
       
   537 
       
   538             this.media[ name ]();
       
   539 
       
   540             return this;
       
   541           }
       
   542 
       
   543           if ( arg != null ) {
       
   544             // Capture the current value of the attribute property
       
   545             previous = this.media[ name ];
       
   546 
       
   547             // Set the attribute property with the new value
       
   548             this.media[ name ] = arg;
       
   549 
       
   550             // If the new value is not the same as the old value
       
   551             // emit an "attrchanged event"
       
   552             if ( previous !== arg ) {
       
   553               this.emit( "attrchange", {
       
   554                 attribute: name,
       
   555                 previousValue: previous,
       
   556                 currentValue: arg
       
   557               });
       
   558             }
       
   559             return this;
       
   560           }
       
   561 
       
   562           return this.media[ name ];
       
   563         };
       
   564       });
       
   565 
       
   566       return ret;
       
   567 
       
   568     })()
       
   569   );
       
   570 
       
   571   Popcorn.forEach( "enable disable".split(" "), function( method ) {
       
   572     Popcorn.p[ method ] = function( plugin ) {
       
   573       return Popcorn[ method ]( this, plugin );
       
   574     };
       
   575   });
       
   576 
       
   577   Popcorn.extend(Popcorn.p, {
       
   578 
       
   579     //  Rounded currentTime
       
   580     roundTime: function() {
       
   581       return Math.round( this.media.currentTime );
       
   582     },
       
   583 
       
   584     //  Attach an event to a single point in time
       
   585     exec: function( id, time, fn ) {
       
   586       var length = arguments.length,
       
   587           trackEvent, sec;
       
   588 
       
   589       // Check if first could possibly be a SMPTE string
       
   590       // p.cue( "smpte string", fn );
       
   591       // try/catch avoid awful throw in Popcorn.util.toSeconds
       
   592       // TODO: Get rid of that, replace with NaN return?
       
   593       try {
       
   594         sec = Popcorn.util.toSeconds( id );
       
   595       } catch ( e ) {}
       
   596 
       
   597       // If it can be converted into a number then
       
   598       // it's safe to assume that the string was SMPTE
       
   599       if ( typeof sec === "number" ) {
       
   600         id = sec;
       
   601       }
       
   602 
       
   603       // Shift arguments based on use case
       
   604       //
       
   605       // Back compat for:
       
   606       // p.cue( time, fn );
       
   607       if ( typeof id === "number" && length === 2 ) {
       
   608         fn = time;
       
   609         time = id;
       
   610         id = Popcorn.guid( "cue" );
       
   611       } else {
       
   612         // Support for new forms
       
   613 
       
   614         // p.cue( "empty-cue" );
       
   615         if ( length === 1 ) {
       
   616           // Set a time for an empty cue. It's not important what
       
   617           // the time actually is, because the cue is a no-op
       
   618           time = -1;
       
   619 
       
   620         } else {
       
   621 
       
   622           // Get the trackEvent that matches the given id.
       
   623           trackEvent = this.getTrackEvent( id );
       
   624 
       
   625           if ( trackEvent ) {
       
   626 
       
   627             // p.cue( "my-id", 12 );
       
   628             // p.cue( "my-id", function() { ... });
       
   629             if ( typeof id === "string" && length === 2 ) {
       
   630 
       
   631               // p.cue( "my-id", 12 );
       
   632               // The path will update the cue time.
       
   633               if ( typeof time === "number" ) {
       
   634                 // Re-use existing trackEvent start callback
       
   635                 fn = trackEvent._natives.start;
       
   636               }
       
   637 
       
   638               // p.cue( "my-id", function() { ... });
       
   639               // The path will update the cue function
       
   640               if ( typeof time === "function" ) {
       
   641                 fn = time;
       
   642                 // Re-use existing trackEvent start time
       
   643                 time = trackEvent.start;
       
   644               }
       
   645             }
       
   646           } else {
       
   647 
       
   648             if ( length >= 2 ) {
       
   649 
       
   650               // p.cue( "a", "00:00:00");
       
   651               if ( typeof time === "string" ) {
       
   652                 try {
       
   653                   sec = Popcorn.util.toSeconds( time );
       
   654                 } catch ( e ) {}
       
   655 
       
   656                 time = sec;
       
   657               }
       
   658 
       
   659               // p.cue( "b", 11 );
       
   660               if ( typeof time === "number" ) {
       
   661                 fn = Popcorn.nop();
       
   662               }
       
   663 
       
   664               // p.cue( "c", function() {});
       
   665               if ( typeof time === "function" ) {
       
   666                 fn = time;
       
   667                 time = -1;
       
   668               }
       
   669             }
       
   670           }
       
   671         }
       
   672       }
       
   673 
       
   674       //  Creating a one second track event with an empty end
       
   675       //  Or update an existing track event with new values
       
   676       Popcorn.addTrackEvent( this, {
       
   677         id: id,
       
   678         start: time,
       
   679         end: time + 1,
       
   680         _running: false,
       
   681         _natives: {
       
   682           start: fn || Popcorn.nop,
       
   683           end: Popcorn.nop,
       
   684           type: "cue"
       
   685         }
       
   686       });
       
   687 
       
   688       return this;
       
   689     },
       
   690 
       
   691     // Mute the calling media, optionally toggle
       
   692     mute: function( toggle ) {
       
   693 
       
   694       var event = toggle == null || toggle === true ? "muted" : "unmuted";
       
   695 
       
   696       // If `toggle` is explicitly `false`,
       
   697       // unmute the media and restore the volume level
       
   698       if ( event === "unmuted" ) {
       
   699         this.media.muted = false;
       
   700         this.media.volume = this.data.state.volume;
       
   701       }
       
   702 
       
   703       // If `toggle` is either null or undefined,
       
   704       // save the current volume and mute the media element
       
   705       if ( event === "muted" ) {
       
   706         this.data.state.volume = this.media.volume;
       
   707         this.media.muted = true;
       
   708       }
       
   709 
       
   710       // Trigger either muted|unmuted event
       
   711       this.emit( event );
       
   712 
       
   713       return this;
       
   714     },
       
   715 
       
   716     // Convenience method, unmute the calling media
       
   717     unmute: function( toggle ) {
       
   718 
       
   719       return this.mute( toggle == null ? false : !toggle );
       
   720     },
       
   721 
       
   722     // Get the client bounding box of an instance element
       
   723     position: function() {
       
   724       return Popcorn.position( this.media );
       
   725     },
       
   726 
       
   727     // Toggle a plugin's playback behaviour (on or off) per instance
       
   728     toggle: function( plugin ) {
       
   729       return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin );
       
   730     },
       
   731 
       
   732     // Set default values for plugin options objects per instance
       
   733     defaults: function( plugin, defaults ) {
       
   734 
       
   735       // If an array of default configurations is provided,
       
   736       // iterate and apply each to this instance
       
   737       if ( Popcorn.isArray( plugin ) ) {
       
   738 
       
   739         Popcorn.forEach( plugin, function( obj ) {
       
   740           for ( var name in obj ) {
       
   741             this.defaults( name, obj[ name ] );
       
   742           }
       
   743         }, this );
       
   744 
       
   745         return this;
       
   746       }
       
   747 
       
   748       if ( !this.options.defaults ) {
       
   749         this.options.defaults = {};
       
   750       }
       
   751 
       
   752       if ( !this.options.defaults[ plugin ] ) {
       
   753         this.options.defaults[ plugin ] = {};
       
   754       }
       
   755 
       
   756       Popcorn.extend( this.options.defaults[ plugin ], defaults );
       
   757 
       
   758       return this;
       
   759     }
       
   760   });
       
   761 
       
   762   Popcorn.Events  = {
       
   763     UIEvents: "blur focus focusin focusout load resize scroll unload",
       
   764     MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
       
   765     Events: "loadstart progress suspend emptied stalled play pause error " +
       
   766             "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
       
   767             "seeking seeked timeupdate ended ratechange durationchange volumechange"
       
   768   };
       
   769 
       
   770   Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
       
   771                            Popcorn.Events.MouseEvents + " " +
       
   772                            Popcorn.Events.Events;
       
   773 
       
   774   internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
       
   775 
       
   776   // Privately compile events table at load time
       
   777   (function( events, data ) {
       
   778 
       
   779     var apis = internal.events.apiTypes,
       
   780     eventsList = events.Natives.split( /\s+/g ),
       
   781     idx = 0, len = eventsList.length, prop;
       
   782 
       
   783     for( ; idx < len; idx++ ) {
       
   784       data.hash[ eventsList[idx] ] = true;
       
   785     }
       
   786 
       
   787     apis.forEach(function( val, idx ) {
       
   788 
       
   789       data.apis[ val ] = {};
       
   790 
       
   791       var apiEvents = events[ val ].split( /\s+/g ),
       
   792       len = apiEvents.length,
       
   793       k = 0;
       
   794 
       
   795       for ( ; k < len; k++ ) {
       
   796         data.apis[ val ][ apiEvents[ k ] ] = true;
       
   797       }
       
   798     });
       
   799   })( Popcorn.Events, internal.events );
       
   800 
       
   801   Popcorn.events = {
       
   802 
       
   803     isNative: function( type ) {
       
   804       return !!internal.events.hash[ type ];
       
   805     },
       
   806     getInterface: function( type ) {
       
   807 
       
   808       if ( !Popcorn.events.isNative( type ) ) {
       
   809         return false;
       
   810       }
       
   811 
       
   812       var eventApi = internal.events,
       
   813         apis = eventApi.apiTypes,
       
   814         apihash = eventApi.apis,
       
   815         idx = 0, len = apis.length, api, tmp;
       
   816 
       
   817       for ( ; idx < len; idx++ ) {
       
   818         tmp = apis[ idx ];
       
   819 
       
   820         if ( apihash[ tmp ][ type ] ) {
       
   821           api = tmp;
       
   822           break;
       
   823         }
       
   824       }
       
   825       return api;
       
   826     },
       
   827     //  Compile all native events to single array
       
   828     all: Popcorn.Events.Natives.split( /\s+/g ),
       
   829     //  Defines all Event handling static functions
       
   830     fn: {
       
   831       trigger: function( type, data ) {
       
   832 
       
   833         var eventInterface, evt;
       
   834         //  setup checks for custom event system
       
   835         if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
       
   836 
       
   837           eventInterface  = Popcorn.events.getInterface( type );
       
   838 
       
   839           if ( eventInterface ) {
       
   840 
       
   841             evt = document.createEvent( eventInterface );
       
   842             evt.initEvent( type, true, true, global, 1 );
       
   843 
       
   844             this.media.dispatchEvent( evt );
       
   845 
       
   846             return this;
       
   847           }
       
   848 
       
   849           //  Custom events
       
   850           Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
       
   851 
       
   852             obj.call( this, data );
       
   853 
       
   854           }, this );
       
   855 
       
   856         }
       
   857 
       
   858         return this;
       
   859       },
       
   860       listen: function( type, fn ) {
       
   861 
       
   862         var self = this,
       
   863             hasEvents = true,
       
   864             eventHook = Popcorn.events.hooks[ type ],
       
   865             origType = type,
       
   866             tmp;
       
   867 
       
   868         if ( !this.data.events[ type ] ) {
       
   869           this.data.events[ type ] = {};
       
   870           hasEvents = false;
       
   871         }
       
   872 
       
   873         // Check and setup event hooks
       
   874         if ( eventHook ) {
       
   875 
       
   876           // Execute hook add method if defined
       
   877           if ( eventHook.add ) {
       
   878             eventHook.add.call( this, {}, fn );
       
   879           }
       
   880 
       
   881           // Reassign event type to our piggyback event type if defined
       
   882           if ( eventHook.bind ) {
       
   883             type = eventHook.bind;
       
   884           }
       
   885 
       
   886           // Reassign handler if defined
       
   887           if ( eventHook.handler ) {
       
   888             tmp = fn;
       
   889 
       
   890             fn = function wrapper( event ) {
       
   891               eventHook.handler.call( self, event, tmp );
       
   892             };
       
   893           }
       
   894 
       
   895           // assume the piggy back event is registered
       
   896           hasEvents = true;
       
   897 
       
   898           // Setup event registry entry
       
   899           if ( !this.data.events[ type ] ) {
       
   900             this.data.events[ type ] = {};
       
   901             // Toggle if the previous assumption was untrue
       
   902             hasEvents = false;
       
   903           }
       
   904         }
       
   905 
       
   906         //  Register event and handler
       
   907         this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
       
   908 
       
   909         // only attach one event of any type
       
   910         if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
       
   911 
       
   912           this.media.addEventListener( type, function( event ) {
       
   913 
       
   914             Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
       
   915               if ( typeof obj === "function" ) {
       
   916                 obj.call( self, event );
       
   917               }
       
   918             });
       
   919 
       
   920           }, false);
       
   921         }
       
   922         return this;
       
   923       },
       
   924       unlisten: function( type, fn ) {
       
   925 
       
   926         if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
       
   927 
       
   928           delete this.data.events[ type ][ fn ];
       
   929 
       
   930           return this;
       
   931         }
       
   932 
       
   933         this.data.events[ type ] = null;
       
   934 
       
   935         return this;
       
   936       }
       
   937     },
       
   938     hooks: {
       
   939       canplayall: {
       
   940         bind: "canplaythrough",
       
   941         add: function( event, callback ) {
       
   942 
       
   943           var state = false;
       
   944 
       
   945           if ( this.media.readyState ) {
       
   946 
       
   947             callback.call( this, event );
       
   948 
       
   949             state = true;
       
   950           }
       
   951 
       
   952           this.data.hooks.canplayall = {
       
   953             fired: state
       
   954           };
       
   955         },
       
   956         // declare special handling instructions
       
   957         handler: function canplayall( event, callback ) {
       
   958 
       
   959           if ( !this.data.hooks.canplayall.fired ) {
       
   960             // trigger original user callback once
       
   961             callback.call( this, event );
       
   962 
       
   963             this.data.hooks.canplayall.fired = true;
       
   964           }
       
   965         }
       
   966       }
       
   967     }
       
   968   };
       
   969 
       
   970   //  Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
       
   971   //  Extend aliases (on, off, emit)
       
   972   Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) {
       
   973     Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ];
       
   974   });
       
   975 
       
   976   // Internal Only - Adds track events to the instance object
       
   977   Popcorn.addTrackEvent = function( obj, track ) {
       
   978     var trackEvent, isUpdate, eventType;
       
   979 
       
   980     // Do a lookup for existing trackevents with this id
       
   981     if ( track.id ) {
       
   982       trackEvent = obj.getTrackEvent( track.id );
       
   983     }
       
   984 
       
   985     // If a track event by this id currently exists, modify it
       
   986     if ( trackEvent ) {
       
   987       isUpdate = true;
       
   988       // Create a new object with the existing trackEvent
       
   989       // Extend with new track properties
       
   990       track = Popcorn.extend( {}, trackEvent, track );
       
   991 
       
   992       // Remove the existing track from the instance
       
   993       obj.removeTrackEvent( track.id );
       
   994     }
       
   995 
       
   996     // Determine if this track has default options set for it
       
   997     // If so, apply them to the track object
       
   998     if ( track && track._natives && track._natives.type &&
       
   999         ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
       
  1000 
       
  1001       track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
       
  1002     }
       
  1003 
       
  1004     if ( track._natives ) {
       
  1005       //  Supports user defined track event id
       
  1006       track._id = track.id || track._id || Popcorn.guid( track._natives.type );
       
  1007 
       
  1008       //  Push track event ids into the history
       
  1009       obj.data.history.push( track._id );
       
  1010     }
       
  1011 
       
  1012     track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
       
  1013     track.end   = Popcorn.util.toSeconds( track.end, obj.options.framerate );
       
  1014 
       
  1015     //  Store this definition in an array sorted by times
       
  1016     var byStart = obj.data.trackEvents.byStart,
       
  1017         byEnd = obj.data.trackEvents.byEnd,
       
  1018         startIndex, endIndex;
       
  1019 
       
  1020     for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) {
       
  1021 
       
  1022       if ( track.start >= byStart[ startIndex ].start ) {
       
  1023         byStart.splice( startIndex + 1, 0, track );
       
  1024         break;
       
  1025       }
       
  1026     }
       
  1027 
       
  1028     for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) {
       
  1029 
       
  1030       if ( track.end > byEnd[ endIndex ].end ) {
       
  1031         byEnd.splice( endIndex + 1, 0, track );
       
  1032         break;
       
  1033       }
       
  1034     }
       
  1035 
       
  1036     // Display track event immediately if it's enabled and current
       
  1037     if ( track.end > obj.media.currentTime &&
       
  1038         track.start <= obj.media.currentTime ) {
       
  1039 
       
  1040       track._running = true;
       
  1041       obj.data.running[ track._natives.type ].push( track );
       
  1042 
       
  1043       if ( !obj.data.disabled[ track._natives.type ] ) {
       
  1044 
       
  1045         track._natives.start.call( obj, null, track );
       
  1046       }
       
  1047     }
       
  1048 
       
  1049     // update startIndex and endIndex
       
  1050     if ( startIndex <= obj.data.trackEvents.startIndex &&
       
  1051       track.start <= obj.data.trackEvents.previousUpdateTime ) {
       
  1052 
       
  1053       obj.data.trackEvents.startIndex++;
       
  1054     }
       
  1055 
       
  1056     if ( endIndex <= obj.data.trackEvents.endIndex &&
       
  1057       track.end < obj.data.trackEvents.previousUpdateTime ) {
       
  1058 
       
  1059       obj.data.trackEvents.endIndex++;
       
  1060     }
       
  1061 
       
  1062     this.timeUpdate( obj, null, true );
       
  1063 
       
  1064     // Store references to user added trackevents in ref table
       
  1065     if ( track._id ) {
       
  1066       Popcorn.addTrackEvent.ref( obj, track );
       
  1067     }
       
  1068 
       
  1069     // If the call to addTrackEvent was an update/modify call, fire an event
       
  1070     if ( isUpdate ) {
       
  1071 
       
  1072       // Determine appropriate event type to trigger
       
  1073       // they are identical in function, but the naming
       
  1074       // adds some level of intuition for the end developer
       
  1075       // to rely on
       
  1076       if ( track._natives.type === "cue" ) {
       
  1077         eventType = "cuechange";
       
  1078       } else {
       
  1079         eventType = "trackchange";
       
  1080       }
       
  1081 
       
  1082       // Fire an event with change information
       
  1083       obj.emit( eventType, {
       
  1084         id: track.id,
       
  1085         previousValue: {
       
  1086           time: trackEvent.start,
       
  1087           fn: trackEvent._natives.start
       
  1088         },
       
  1089         currentValue: {
       
  1090           time: track.start,
       
  1091           fn: track._natives.start
       
  1092         }
       
  1093       });
       
  1094     }
       
  1095   };
       
  1096 
       
  1097   // Internal Only - Adds track event references to the instance object's trackRefs hash table
       
  1098   Popcorn.addTrackEvent.ref = function( obj, track ) {
       
  1099     obj.data.trackRefs[ track._id ] = track;
       
  1100 
       
  1101     return obj;
       
  1102   };
       
  1103 
       
  1104   Popcorn.removeTrackEvent  = function( obj, removeId ) {
       
  1105 
       
  1106     var start, end, animate,
       
  1107         historyLen = obj.data.history.length,
       
  1108         length = obj.data.trackEvents.byStart.length,
       
  1109         index = 0,
       
  1110         indexWasAt = 0,
       
  1111         byStart = [],
       
  1112         byEnd = [],
       
  1113         animating = [],
       
  1114         history = [];
       
  1115 
       
  1116     while ( --length > -1 ) {
       
  1117       start = obj.data.trackEvents.byStart[ index ];
       
  1118       end = obj.data.trackEvents.byEnd[ index ];
       
  1119 
       
  1120       // Padding events will not have _id properties.
       
  1121       // These should be safely pushed onto the front and back of the
       
  1122       // track event array
       
  1123       if ( !start._id ) {
       
  1124         byStart.push( start );
       
  1125         byEnd.push( end );
       
  1126       }
       
  1127 
       
  1128       // Filter for user track events (vs system track events)
       
  1129       if ( start._id ) {
       
  1130 
       
  1131         // If not a matching start event for removal
       
  1132         if ( start._id !== removeId ) {
       
  1133           byStart.push( start );
       
  1134         }
       
  1135 
       
  1136         // If not a matching end event for removal
       
  1137         if ( end._id !== removeId ) {
       
  1138           byEnd.push( end );
       
  1139         }
       
  1140 
       
  1141         // If the _id is matched, capture the current index
       
  1142         if ( start._id === removeId ) {
       
  1143           indexWasAt = index;
       
  1144 
       
  1145           // If a _teardown function was defined,
       
  1146           // enforce for track event removals
       
  1147           if ( start._natives._teardown ) {
       
  1148             start._natives._teardown.call( obj, start );
       
  1149           }
       
  1150         }
       
  1151       }
       
  1152       // Increment the track index
       
  1153       index++;
       
  1154     }
       
  1155 
       
  1156     // Reset length to be used by the condition below to determine
       
  1157     // if animating track events should also be filtered for removal.
       
  1158     // Reset index below to be used by the reverse while as an
       
  1159     // incrementing counter
       
  1160     length = obj.data.trackEvents.animating.length;
       
  1161     index = 0;
       
  1162 
       
  1163     if ( length ) {
       
  1164       while ( --length > -1 ) {
       
  1165         animate = obj.data.trackEvents.animating[ index ];
       
  1166 
       
  1167         // Padding events will not have _id properties.
       
  1168         // These should be safely pushed onto the front and back of the
       
  1169         // track event array
       
  1170         if ( !animate._id ) {
       
  1171           animating.push( animate );
       
  1172         }
       
  1173 
       
  1174         // If not a matching animate event for removal
       
  1175         if ( animate._id && animate._id !== removeId ) {
       
  1176           animating.push( animate );
       
  1177         }
       
  1178         // Increment the track index
       
  1179         index++;
       
  1180       }
       
  1181     }
       
  1182 
       
  1183     //  Update
       
  1184     if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
       
  1185       obj.data.trackEvents.startIndex--;
       
  1186     }
       
  1187 
       
  1188     if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
       
  1189       obj.data.trackEvents.endIndex--;
       
  1190     }
       
  1191 
       
  1192     obj.data.trackEvents.byStart = byStart;
       
  1193     obj.data.trackEvents.byEnd = byEnd;
       
  1194     obj.data.trackEvents.animating = animating;
       
  1195 
       
  1196     for ( var i = 0; i < historyLen; i++ ) {
       
  1197       if ( obj.data.history[ i ] !== removeId ) {
       
  1198         history.push( obj.data.history[ i ] );
       
  1199       }
       
  1200     }
       
  1201 
       
  1202     // Update ordered history array
       
  1203     obj.data.history = history;
       
  1204 
       
  1205     // Update track event references
       
  1206     Popcorn.removeTrackEvent.ref( obj, removeId );
       
  1207   };
       
  1208 
       
  1209   // Internal Only - Removes track event references from instance object's trackRefs hash table
       
  1210   Popcorn.removeTrackEvent.ref = function( obj, removeId ) {
       
  1211     delete obj.data.trackRefs[ removeId ];
       
  1212 
       
  1213     return obj;
       
  1214   };
       
  1215 
       
  1216   // Return an array of track events bound to this instance object
       
  1217   Popcorn.getTrackEvents = function( obj ) {
       
  1218 
       
  1219     var trackevents = [],
       
  1220       refs = obj.data.trackEvents.byStart,
       
  1221       length = refs.length,
       
  1222       idx = 0,
       
  1223       ref;
       
  1224 
       
  1225     for ( ; idx < length; idx++ ) {
       
  1226       ref = refs[ idx ];
       
  1227       // Return only user attributed track event references
       
  1228       if ( ref._id ) {
       
  1229         trackevents.push( ref );
       
  1230       }
       
  1231     }
       
  1232 
       
  1233     return trackevents;
       
  1234   };
       
  1235 
       
  1236   // Internal Only - Returns an instance object's trackRefs hash table
       
  1237   Popcorn.getTrackEvents.ref = function( obj ) {
       
  1238     return obj.data.trackRefs;
       
  1239   };
       
  1240 
       
  1241   // Return a single track event bound to this instance object
       
  1242   Popcorn.getTrackEvent = function( obj, trackId ) {
       
  1243     return obj.data.trackRefs[ trackId ];
       
  1244   };
       
  1245 
       
  1246   // Internal Only - Returns an instance object's track reference by track id
       
  1247   Popcorn.getTrackEvent.ref = function( obj, trackId ) {
       
  1248     return obj.data.trackRefs[ trackId ];
       
  1249   };
       
  1250 
       
  1251   Popcorn.getLastTrackEventId = function( obj ) {
       
  1252     return obj.data.history[ obj.data.history.length - 1 ];
       
  1253   };
       
  1254 
       
  1255   Popcorn.timeUpdate = function( obj, event ) {
       
  1256 
       
  1257     var currentTime = obj.media.currentTime,
       
  1258         previousTime = obj.data.trackEvents.previousUpdateTime,
       
  1259         tracks = obj.data.trackEvents,
       
  1260         end = tracks.endIndex,
       
  1261         start = tracks.startIndex,
       
  1262         byStartLen = tracks.byStart.length,
       
  1263         byEndLen = tracks.byEnd.length,
       
  1264         registryByName = Popcorn.registryByName,
       
  1265         trackstart = "trackstart",
       
  1266         trackend = "trackend",
       
  1267 
       
  1268         byEnd, byStart, byAnimate, natives, type, runningPlugins;
       
  1269 
       
  1270     //  Playbar advancing
       
  1271     if ( previousTime <= currentTime ) {
       
  1272 
       
  1273       while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
       
  1274 
       
  1275         byEnd = tracks.byEnd[ end ];
       
  1276         natives = byEnd._natives;
       
  1277         type = natives && natives.type;
       
  1278 
       
  1279         //  If plugin does not exist on this instance, remove it
       
  1280         if ( !natives ||
       
  1281             ( !!registryByName[ type ] ||
       
  1282               !!obj[ type ] ) ) {
       
  1283 
       
  1284           if ( byEnd._running === true ) {
       
  1285 
       
  1286             byEnd._running = false;
       
  1287             runningPlugins = obj.data.running[ type ];
       
  1288             runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 );
       
  1289 
       
  1290             if ( !obj.data.disabled[ type ] ) {
       
  1291 
       
  1292               natives.end.call( obj, event, byEnd );
       
  1293 
       
  1294               obj.emit( trackend,
       
  1295                 Popcorn.extend({}, byEnd, {
       
  1296                   plugin: type,
       
  1297                   type: trackend
       
  1298                 })
       
  1299               );
       
  1300             }
       
  1301           }
       
  1302 
       
  1303           end++;
       
  1304         } else {
       
  1305           // remove track event
       
  1306           Popcorn.removeTrackEvent( obj, byEnd._id );
       
  1307           return;
       
  1308         }
       
  1309       }
       
  1310 
       
  1311       while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
       
  1312 
       
  1313         byStart = tracks.byStart[ start ];
       
  1314         natives = byStart._natives;
       
  1315         type = natives && natives.type;
       
  1316 
       
  1317         //  If plugin does not exist on this instance, remove it
       
  1318         if ( !natives ||
       
  1319             ( !!registryByName[ type ] ||
       
  1320               !!obj[ type ] ) ) {
       
  1321 
       
  1322           if ( byStart.end > currentTime &&
       
  1323                 byStart._running === false ) {
       
  1324 
       
  1325             byStart._running = true;
       
  1326             obj.data.running[ type ].push( byStart );
       
  1327 
       
  1328             if ( !obj.data.disabled[ type ] ) {
       
  1329 
       
  1330               natives.start.call( obj, event, byStart );
       
  1331 
       
  1332               obj.emit( trackstart,
       
  1333                 Popcorn.extend({}, byStart, {
       
  1334                   plugin: type,
       
  1335                   type: trackstart
       
  1336                 })
       
  1337               );
       
  1338             }
       
  1339           }
       
  1340           start++;
       
  1341         } else {
       
  1342           // remove track event
       
  1343           Popcorn.removeTrackEvent( obj, byStart._id );
       
  1344           return;
       
  1345         }
       
  1346       }
       
  1347 
       
  1348     // Playbar receding
       
  1349     } else if ( previousTime > currentTime ) {
       
  1350 
       
  1351       while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
       
  1352 
       
  1353         byStart = tracks.byStart[ start ];
       
  1354         natives = byStart._natives;
       
  1355         type = natives && natives.type;
       
  1356 
       
  1357         // if plugin does not exist on this instance, remove it
       
  1358         if ( !natives ||
       
  1359             ( !!registryByName[ type ] ||
       
  1360               !!obj[ type ] ) ) {
       
  1361 
       
  1362           if ( byStart._running === true ) {
       
  1363 
       
  1364             byStart._running = false;
       
  1365             runningPlugins = obj.data.running[ type ];
       
  1366             runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 );
       
  1367 
       
  1368             if ( !obj.data.disabled[ type ] ) {
       
  1369 
       
  1370               natives.end.call( obj, event, byStart );
       
  1371 
       
  1372               obj.emit( trackend,
       
  1373                 Popcorn.extend({}, byStart, {
       
  1374                   plugin: type,
       
  1375                   type: trackend
       
  1376                 })
       
  1377               );
       
  1378             }
       
  1379           }
       
  1380           start--;
       
  1381         } else {
       
  1382           // remove track event
       
  1383           Popcorn.removeTrackEvent( obj, byStart._id );
       
  1384           return;
       
  1385         }
       
  1386       }
       
  1387 
       
  1388       while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
       
  1389 
       
  1390         byEnd = tracks.byEnd[ end ];
       
  1391         natives = byEnd._natives;
       
  1392         type = natives && natives.type;
       
  1393 
       
  1394         // if plugin does not exist on this instance, remove it
       
  1395         if ( !natives ||
       
  1396             ( !!registryByName[ type ] ||
       
  1397               !!obj[ type ] ) ) {
       
  1398 
       
  1399           if ( byEnd.start <= currentTime &&
       
  1400                 byEnd._running === false ) {
       
  1401 
       
  1402             byEnd._running = true;
       
  1403             obj.data.running[ type ].push( byEnd );
       
  1404 
       
  1405             if ( !obj.data.disabled[ type ] ) {
       
  1406 
       
  1407               natives.start.call( obj, event, byEnd );
       
  1408 
       
  1409               obj.emit( trackstart,
       
  1410                 Popcorn.extend({}, byEnd, {
       
  1411                   plugin: type,
       
  1412                   type: trackstart
       
  1413                 })
       
  1414               );
       
  1415             }
       
  1416           }
       
  1417           end--;
       
  1418         } else {
       
  1419           // remove track event
       
  1420           Popcorn.removeTrackEvent( obj, byEnd._id );
       
  1421           return;
       
  1422         }
       
  1423       }
       
  1424     }
       
  1425 
       
  1426     tracks.endIndex = end;
       
  1427     tracks.startIndex = start;
       
  1428     tracks.previousUpdateTime = currentTime;
       
  1429 
       
  1430     //enforce index integrity if trackRemoved
       
  1431     tracks.byStart.length < byStartLen && tracks.startIndex--;
       
  1432     tracks.byEnd.length < byEndLen && tracks.endIndex--;
       
  1433 
       
  1434   };
       
  1435 
       
  1436   //  Map and Extend TrackEvent functions to all Popcorn instances
       
  1437   Popcorn.extend( Popcorn.p, {
       
  1438 
       
  1439     getTrackEvents: function() {
       
  1440       return Popcorn.getTrackEvents.call( null, this );
       
  1441     },
       
  1442 
       
  1443     getTrackEvent: function( id ) {
       
  1444       return Popcorn.getTrackEvent.call( null, this, id );
       
  1445     },
       
  1446 
       
  1447     getLastTrackEventId: function() {
       
  1448       return Popcorn.getLastTrackEventId.call( null, this );
       
  1449     },
       
  1450 
       
  1451     removeTrackEvent: function( id ) {
       
  1452 
       
  1453       Popcorn.removeTrackEvent.call( null, this, id );
       
  1454       return this;
       
  1455     },
       
  1456 
       
  1457     removePlugin: function( name ) {
       
  1458       Popcorn.removePlugin.call( null, this, name );
       
  1459       return this;
       
  1460     },
       
  1461 
       
  1462     timeUpdate: function( event ) {
       
  1463       Popcorn.timeUpdate.call( null, this, event );
       
  1464       return this;
       
  1465     },
       
  1466 
       
  1467     destroy: function() {
       
  1468       Popcorn.destroy.call( null, this );
       
  1469       return this;
       
  1470     }
       
  1471   });
       
  1472 
       
  1473   //  Plugin manifests
       
  1474   Popcorn.manifest = {};
       
  1475   //  Plugins are registered
       
  1476   Popcorn.registry = [];
       
  1477   Popcorn.registryByName = {};
       
  1478   //  An interface for extending Popcorn
       
  1479   //  with plugin functionality
       
  1480   Popcorn.plugin = function( name, definition, manifest ) {
       
  1481 
       
  1482     if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  1483       Popcorn.error( "'" + name + "' is a protected function name" );
       
  1484       return;
       
  1485     }
       
  1486 
       
  1487     //  Provides some sugar, but ultimately extends
       
  1488     //  the definition into Popcorn.p
       
  1489     var reserved = [ "start", "end" ],
       
  1490         plugin = {},
       
  1491         setup,
       
  1492         isfn = typeof definition === "function",
       
  1493         methods = [ "_setup", "_teardown", "start", "end", "frame" ];
       
  1494 
       
  1495     // combines calls of two function calls into one
       
  1496     var combineFn = function( first, second ) {
       
  1497 
       
  1498       first = first || Popcorn.nop;
       
  1499       second = second || Popcorn.nop;
       
  1500 
       
  1501       return function() {
       
  1502         first.apply( this, arguments );
       
  1503         second.apply( this, arguments );
       
  1504       };
       
  1505     };
       
  1506 
       
  1507     //  If `manifest` arg is undefined, check for manifest within the `definition` object
       
  1508     //  If no `definition.manifest`, an empty object is a sufficient fallback
       
  1509     Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
       
  1510 
       
  1511     // apply safe, and empty default functions
       
  1512     methods.forEach(function( method ) {
       
  1513       definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
       
  1514     });
       
  1515 
       
  1516     var pluginFn = function( setup, options ) {
       
  1517 
       
  1518       if ( !options ) {
       
  1519         return this;
       
  1520       }
       
  1521 
       
  1522       // When the "ranges" property is set and its value is an array, short-circuit
       
  1523       // the pluginFn definition to recall itself with an options object generated from
       
  1524       // each range object in the ranges array. (eg. { start: 15, end: 16 } )
       
  1525       if ( options.ranges && Popcorn.isArray(options.ranges) ) {
       
  1526         Popcorn.forEach( options.ranges, function( range ) {
       
  1527           // Create a fresh object, extend with current options
       
  1528           // and start/end range object's properties
       
  1529           // Works with in/out as well.
       
  1530           var opts = Popcorn.extend( {}, options, range );
       
  1531 
       
  1532           // Remove the ranges property to prevent infinitely
       
  1533           // entering this condition
       
  1534           delete opts.ranges;
       
  1535 
       
  1536           // Call the plugin with the newly created opts object
       
  1537           this[ name ]( opts );
       
  1538         }, this);
       
  1539 
       
  1540         // Return the Popcorn instance to avoid creating an empty track event
       
  1541         return this;
       
  1542       }
       
  1543 
       
  1544       //  Storing the plugin natives
       
  1545       var natives = options._natives = {},
       
  1546           compose = "",
       
  1547           originalOpts, manifestOpts;
       
  1548 
       
  1549       Popcorn.extend( natives, setup );
       
  1550 
       
  1551       options._natives.type = name;
       
  1552       options._running = false;
       
  1553 
       
  1554       natives.start = natives.start || natives[ "in" ];
       
  1555       natives.end = natives.end || natives[ "out" ];
       
  1556 
       
  1557       if ( options.once ) {
       
  1558         natives.end = combineFn( natives.end, function() {
       
  1559           this.removeTrackEvent( options._id );
       
  1560         });
       
  1561       }
       
  1562 
       
  1563       // extend teardown to always call end if running
       
  1564       natives._teardown = combineFn(function() {
       
  1565 
       
  1566         var args = slice.call( arguments ),
       
  1567             runningPlugins = this.data.running[ natives.type ];
       
  1568 
       
  1569         // end function signature is not the same as teardown,
       
  1570         // put null on the front of arguments for the event parameter
       
  1571         args.unshift( null );
       
  1572 
       
  1573         // only call end if event is running
       
  1574         args[ 1 ]._running &&
       
  1575           runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) &&
       
  1576           natives.end.apply( this, args );
       
  1577       }, natives._teardown );
       
  1578 
       
  1579       // default to an empty string if no effect exists
       
  1580       // split string into an array of effects
       
  1581       options.compose = options.compose && options.compose.split( " " ) || [];
       
  1582       options.effect = options.effect && options.effect.split( " " ) || [];
       
  1583 
       
  1584       // join the two arrays together
       
  1585       options.compose = options.compose.concat( options.effect );
       
  1586 
       
  1587       options.compose.forEach(function( composeOption ) {
       
  1588 
       
  1589         // if the requested compose is garbage, throw it away
       
  1590         compose = Popcorn.compositions[ composeOption ] || {};
       
  1591 
       
  1592         // extends previous functions with compose function
       
  1593         methods.forEach(function( method ) {
       
  1594           natives[ method ] = combineFn( natives[ method ], compose[ method ] );
       
  1595         });
       
  1596       });
       
  1597 
       
  1598       //  Ensure a manifest object, an empty object is a sufficient fallback
       
  1599       options._natives.manifest = manifest;
       
  1600 
       
  1601       //  Checks for expected properties
       
  1602       if ( !( "start" in options ) ) {
       
  1603         options.start = options[ "in" ] || 0;
       
  1604       }
       
  1605 
       
  1606       if ( !options.end && options.end !== 0 ) {
       
  1607         options.end = options[ "out" ] || Number.MAX_VALUE;
       
  1608       }
       
  1609 
       
  1610       // Use hasOwn to detect non-inherited toString, since all
       
  1611       // objects will receive a toString - its otherwise undetectable
       
  1612       if ( !hasOwn.call( options, "toString" ) ) {
       
  1613         options.toString = function() {
       
  1614           var props = [
       
  1615             "start: " + options.start,
       
  1616             "end: " + options.end,
       
  1617             "id: " + (options.id || options._id)
       
  1618           ];
       
  1619 
       
  1620           // Matches null and undefined, allows: false, 0, "" and truthy
       
  1621           if ( options.target != null ) {
       
  1622             props.push( "target: " + options.target );
       
  1623           }
       
  1624 
       
  1625           return name + " ( " + props.join(", ") + " )";
       
  1626         };
       
  1627       }
       
  1628 
       
  1629       // Resolves 239, 241, 242
       
  1630       if ( !options.target ) {
       
  1631 
       
  1632         //  Sometimes the manifest may be missing entirely
       
  1633         //  or it has an options object that doesn't have a `target` property
       
  1634         manifestOpts = "options" in manifest && manifest.options;
       
  1635 
       
  1636         options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
       
  1637       }
       
  1638 
       
  1639       if ( options._natives ) {
       
  1640         // ensure an initial id is there before setup is called
       
  1641         options._id = Popcorn.guid( options._natives.type );
       
  1642       }
       
  1643 
       
  1644       // Trigger _setup method if exists
       
  1645       options._natives._setup && options._natives._setup.call( this, options );
       
  1646 
       
  1647       // Create new track event for this instance
       
  1648       Popcorn.addTrackEvent( this, options );
       
  1649 
       
  1650       //  Future support for plugin event definitions
       
  1651       //  for all of the native events
       
  1652       Popcorn.forEach( setup, function( callback, type ) {
       
  1653 
       
  1654         if ( type !== "type" ) {
       
  1655 
       
  1656           if ( reserved.indexOf( type ) === -1 ) {
       
  1657 
       
  1658             this.on( type, callback );
       
  1659           }
       
  1660         }
       
  1661 
       
  1662       }, this );
       
  1663 
       
  1664       return this;
       
  1665     };
       
  1666 
       
  1667     //  Extend Popcorn.p with new named definition
       
  1668     //  Assign new named definition
       
  1669     Popcorn.p[ name ] = plugin[ name ] = function( id, options ) {
       
  1670       var length = arguments.length,
       
  1671           trackEvent, defaults, mergedSetupOpts;
       
  1672 
       
  1673       // Shift arguments based on use case
       
  1674       //
       
  1675       // Back compat for:
       
  1676       // p.plugin( options );
       
  1677       if ( id && !options ) {
       
  1678         options = id;
       
  1679         id = null;
       
  1680       } else {
       
  1681 
       
  1682         // Get the trackEvent that matches the given id.
       
  1683         trackEvent = this.getTrackEvent( id );
       
  1684 
       
  1685         // If the track event does not exist, ensure that the options
       
  1686         // object has a proper id
       
  1687         if ( !trackEvent ) {
       
  1688           options.id = id;
       
  1689 
       
  1690         // If the track event does exist, merge the updated properties
       
  1691         } else {
       
  1692 
       
  1693           options = Popcorn.extend( {}, trackEvent, options );
       
  1694 
       
  1695           Popcorn.addTrackEvent( this, options );
       
  1696 
       
  1697           return this;
       
  1698         }
       
  1699       }
       
  1700 
       
  1701       this.data.running[ name ] = this.data.running[ name ] || [];
       
  1702 
       
  1703       // Merge with defaults if they exist, make sure per call is prioritized
       
  1704       defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {};
       
  1705       mergedSetupOpts = Popcorn.extend( {}, defaults, options );
       
  1706 
       
  1707       return pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition,
       
  1708                                   mergedSetupOpts );
       
  1709     };
       
  1710 
       
  1711     // if the manifest parameter exists we should extend it onto the definition object
       
  1712     // so that it shows up when calling Popcorn.registry and Popcorn.registryByName
       
  1713     if ( manifest ) {
       
  1714       Popcorn.extend( definition, {
       
  1715         manifest: manifest
       
  1716       });
       
  1717     }
       
  1718 
       
  1719     //  Push into the registry
       
  1720     var entry = {
       
  1721       fn: plugin[ name ],
       
  1722       definition: definition,
       
  1723       base: definition,
       
  1724       parents: [],
       
  1725       name: name
       
  1726     };
       
  1727     Popcorn.registry.push(
       
  1728        Popcorn.extend( plugin, entry, {
       
  1729         type: name
       
  1730       })
       
  1731     );
       
  1732     Popcorn.registryByName[ name ] = entry;
       
  1733 
       
  1734     return plugin;
       
  1735   };
       
  1736 
       
  1737   // Storage for plugin function errors
       
  1738   Popcorn.plugin.errors = [];
       
  1739 
       
  1740   // Returns wrapped plugin function
       
  1741   function safeTry( fn, pluginName ) {
       
  1742     return function() {
       
  1743 
       
  1744       //  When Popcorn.plugin.debug is true, do not suppress errors
       
  1745       if ( Popcorn.plugin.debug ) {
       
  1746         return fn.apply( this, arguments );
       
  1747       }
       
  1748 
       
  1749       try {
       
  1750         return fn.apply( this, arguments );
       
  1751       } catch ( ex ) {
       
  1752 
       
  1753         // Push plugin function errors into logging queue
       
  1754         Popcorn.plugin.errors.push({
       
  1755           plugin: pluginName,
       
  1756           thrown: ex,
       
  1757           source: fn.toString()
       
  1758         });
       
  1759 
       
  1760         // Trigger an error that the instance can listen for
       
  1761         // and react to
       
  1762         this.emit( "pluginerror", Popcorn.plugin.errors );
       
  1763       }
       
  1764     };
       
  1765   }
       
  1766 
       
  1767   // Debug-mode flag for plugin development
       
  1768   // True for Popcorn development versions, false for stable/tagged versions
       
  1769   Popcorn.plugin.debug = ( Popcorn.version === "@" + "VERSION" );
       
  1770 
       
  1771   //  removePlugin( type ) removes all tracks of that from all instances of popcorn
       
  1772   //  removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
       
  1773   Popcorn.removePlugin = function( obj, name ) {
       
  1774 
       
  1775     //  Check if we are removing plugin from an instance or from all of Popcorn
       
  1776     if ( !name ) {
       
  1777 
       
  1778       //  Fix the order
       
  1779       name = obj;
       
  1780       obj = Popcorn.p;
       
  1781 
       
  1782       if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  1783         Popcorn.error( "'" + name + "' is a protected function name" );
       
  1784         return;
       
  1785       }
       
  1786 
       
  1787       var registryLen = Popcorn.registry.length,
       
  1788           registryIdx;
       
  1789 
       
  1790       // remove plugin reference from registry
       
  1791       for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
       
  1792         if ( Popcorn.registry[ registryIdx ].name === name ) {
       
  1793           Popcorn.registry.splice( registryIdx, 1 );
       
  1794           delete Popcorn.registryByName[ name ];
       
  1795           delete Popcorn.manifest[ name ];
       
  1796 
       
  1797           // delete the plugin
       
  1798           delete obj[ name ];
       
  1799 
       
  1800           // plugin found and removed, stop checking, we are done
       
  1801           return;
       
  1802         }
       
  1803       }
       
  1804 
       
  1805     }
       
  1806 
       
  1807     var byStart = obj.data.trackEvents.byStart,
       
  1808         byEnd = obj.data.trackEvents.byEnd,
       
  1809         animating = obj.data.trackEvents.animating,
       
  1810         idx, sl;
       
  1811 
       
  1812     // remove all trackEvents
       
  1813     for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
       
  1814 
       
  1815       if ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) {
       
  1816 
       
  1817         byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
       
  1818 
       
  1819         byStart.splice( idx, 1 );
       
  1820 
       
  1821         // update for loop if something removed, but keep checking
       
  1822         idx--; sl--;
       
  1823         if ( obj.data.trackEvents.startIndex <= idx ) {
       
  1824           obj.data.trackEvents.startIndex--;
       
  1825           obj.data.trackEvents.endIndex--;
       
  1826         }
       
  1827       }
       
  1828 
       
  1829       // clean any remaining references in the end index
       
  1830       // we do this seperate from the above check because they might not be in the same order
       
  1831       if ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) {
       
  1832 
       
  1833         byEnd.splice( idx, 1 );
       
  1834       }
       
  1835     }
       
  1836 
       
  1837     //remove all animating events
       
  1838     for ( idx = 0, sl = animating.length; idx < sl; idx++ ) {
       
  1839 
       
  1840       if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) {
       
  1841 
       
  1842         animating.splice( idx, 1 );
       
  1843 
       
  1844         // update for loop if something removed, but keep checking
       
  1845         idx--; sl--;
       
  1846       }
       
  1847     }
       
  1848 
       
  1849   };
       
  1850 
       
  1851   Popcorn.compositions = {};
       
  1852 
       
  1853   //  Plugin inheritance
       
  1854   Popcorn.compose = function( name, definition, manifest ) {
       
  1855 
       
  1856     //  If `manifest` arg is undefined, check for manifest within the `definition` object
       
  1857     //  If no `definition.manifest`, an empty object is a sufficient fallback
       
  1858     Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
       
  1859 
       
  1860     // register the effect by name
       
  1861     Popcorn.compositions[ name ] = definition;
       
  1862   };
       
  1863 
       
  1864   Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
       
  1865 
       
  1866   var rnaiveExpr = /^(?:\.|#|\[)/;
       
  1867 
       
  1868   //  Basic DOM utilities and helpers API. See #1037
       
  1869   Popcorn.dom = {
       
  1870     debug: false,
       
  1871     //  Popcorn.dom.find( selector, context )
       
  1872     //
       
  1873     //  Returns the first element that matches the specified selector
       
  1874     //  Optionally provide a context element, defaults to `document`
       
  1875     //
       
  1876     //  eg.
       
  1877     //  Popcorn.dom.find("video") returns the first video element
       
  1878     //  Popcorn.dom.find("#foo") returns the first element with `id="foo"`
       
  1879     //  Popcorn.dom.find("foo") returns the first element with `id="foo"`
       
  1880     //     Note: Popcorn.dom.find("foo") is the only allowed deviation
       
  1881     //           from valid querySelector selector syntax
       
  1882     //
       
  1883     //  Popcorn.dom.find(".baz") returns the first element with `class="baz"`
       
  1884     //  Popcorn.dom.find("[preload]") returns the first element with `preload="..."`
       
  1885     //  ...
       
  1886     //  See https://developer.mozilla.org/En/DOM/Document.querySelector
       
  1887     //
       
  1888     //
       
  1889     find: function( selector, context ) {
       
  1890       var node = null;
       
  1891 
       
  1892       //  Trim leading/trailing whitespace to avoid false negatives
       
  1893       selector = selector.trim();
       
  1894 
       
  1895       //  Default context is the `document`
       
  1896       context = context || document;
       
  1897 
       
  1898       if ( selector ) {
       
  1899         //  If the selector does not begin with "#", "." or "[",
       
  1900         //  it could be either a nodeName or ID w/o "#"
       
  1901         if ( !rnaiveExpr.test( selector ) ) {
       
  1902 
       
  1903           //  Try finding an element that matches by ID first
       
  1904           node = document.getElementById( selector );
       
  1905 
       
  1906           //  If a match was found by ID, return the element
       
  1907           if ( node !== null ) {
       
  1908             return node;
       
  1909           }
       
  1910         }
       
  1911         //  Assume no elements have been found yet
       
  1912         //  Catch any invalid selector syntax errors and bury them.
       
  1913         try {
       
  1914           node = context.querySelector( selector );
       
  1915         } catch ( e ) {
       
  1916           if ( Popcorn.dom.debug ) {
       
  1917             throw new Error(e);
       
  1918           }
       
  1919         }
       
  1920       }
       
  1921       return node;
       
  1922     }
       
  1923   };
       
  1924 
       
  1925   //  Cache references to reused RegExps
       
  1926   var rparams = /\?/,
       
  1927   //  XHR Setup object
       
  1928   setup = {
       
  1929     url: "",
       
  1930     data: "",
       
  1931     dataType: "",
       
  1932     success: Popcorn.nop,
       
  1933     type: "GET",
       
  1934     async: true,
       
  1935     xhr: function() {
       
  1936       return new global.XMLHttpRequest();
       
  1937     }
       
  1938   };
       
  1939 
       
  1940   Popcorn.xhr = function( options ) {
       
  1941 
       
  1942     options.dataType = options.dataType && options.dataType.toLowerCase() || null;
       
  1943 
       
  1944     if ( options.dataType &&
       
  1945          ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
       
  1946 
       
  1947       Popcorn.xhr.getJSONP(
       
  1948         options.url,
       
  1949         options.success,
       
  1950         options.dataType === "script"
       
  1951       );
       
  1952       return;
       
  1953     }
       
  1954 
       
  1955     var settings = Popcorn.extend( {}, setup, options );
       
  1956 
       
  1957     //  Create new XMLHttpRequest object
       
  1958     settings.ajax  = settings.xhr();
       
  1959 
       
  1960     if ( settings.ajax ) {
       
  1961 
       
  1962       if ( settings.type === "GET" && settings.data ) {
       
  1963 
       
  1964         //  append query string
       
  1965         settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
       
  1966 
       
  1967         //  Garbage collect and reset settings.data
       
  1968         settings.data = null;
       
  1969       }
       
  1970 
       
  1971 
       
  1972       settings.ajax.open( settings.type, settings.url, settings.async );
       
  1973       settings.ajax.send( settings.data || null );
       
  1974 
       
  1975       return Popcorn.xhr.httpData( settings );
       
  1976     }
       
  1977   };
       
  1978 
       
  1979 
       
  1980   Popcorn.xhr.httpData = function( settings ) {
       
  1981 
       
  1982     var data, json = null,
       
  1983         parser, xml = null;
       
  1984 
       
  1985     settings.ajax.onreadystatechange = function() {
       
  1986 
       
  1987       if ( settings.ajax.readyState === 4 ) {
       
  1988 
       
  1989         try {
       
  1990           json = JSON.parse( settings.ajax.responseText );
       
  1991         } catch( e ) {
       
  1992           //suppress
       
  1993         }
       
  1994 
       
  1995         data = {
       
  1996           xml: settings.ajax.responseXML,
       
  1997           text: settings.ajax.responseText,
       
  1998           json: json
       
  1999         };
       
  2000 
       
  2001         // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml
       
  2002         if ( !data.xml || !data.xml.documentElement ) {
       
  2003           data.xml = null;
       
  2004 
       
  2005           try {
       
  2006             parser = new DOMParser();
       
  2007             xml = parser.parseFromString( settings.ajax.responseText, "text/xml" );
       
  2008 
       
  2009             if ( !xml.getElementsByTagName( "parsererror" ).length ) {
       
  2010               data.xml = xml;
       
  2011             }
       
  2012           } catch ( e ) {
       
  2013             // data.xml remains null
       
  2014           }
       
  2015         }
       
  2016 
       
  2017         //  If a dataType was specified, return that type of data
       
  2018         if ( settings.dataType ) {
       
  2019           data = data[ settings.dataType ];
       
  2020         }
       
  2021 
       
  2022 
       
  2023         settings.success.call( settings.ajax, data );
       
  2024 
       
  2025       }
       
  2026     };
       
  2027     return data;
       
  2028   };
       
  2029 
       
  2030   Popcorn.xhr.getJSONP = function( url, success, isScript ) {
       
  2031 
       
  2032     var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
       
  2033       script = document.createElement( "script" ),
       
  2034       isFired = false,
       
  2035       params = [],
       
  2036       rjsonp = /(=)\?(?=&|$)|\?\?/,
       
  2037       replaceInUrl, prefix, paramStr, callback, callparam;
       
  2038 
       
  2039     if ( !isScript ) {
       
  2040 
       
  2041       // is there a calback already in the url
       
  2042       callparam = url.match( /(callback=[^&]*)/ );
       
  2043 
       
  2044       if ( callparam !== null && callparam.length ) {
       
  2045 
       
  2046         prefix = callparam[ 1 ].split( "=" )[ 1 ];
       
  2047 
       
  2048         // Since we need to support developer specified callbacks
       
  2049         // and placeholders in harmony, make sure matches to "callback="
       
  2050         // aren't just placeholders.
       
  2051         // We coded ourselves into a corner here.
       
  2052         // JSONP callbacks should never have been
       
  2053         // allowed to have developer specified callbacks
       
  2054         if ( prefix === "?" ) {
       
  2055           prefix = "jsonp";
       
  2056         }
       
  2057 
       
  2058         // get the callback name
       
  2059         callback = Popcorn.guid( prefix );
       
  2060 
       
  2061         // replace existing callback name with unique callback name
       
  2062         url = url.replace( /(callback=[^&]*)/, "callback=" + callback );
       
  2063       } else {
       
  2064 
       
  2065         callback = Popcorn.guid( "jsonp" );
       
  2066 
       
  2067         if ( rjsonp.test( url ) ) {
       
  2068           url = url.replace( rjsonp, "$1" + callback );
       
  2069         }
       
  2070 
       
  2071         // split on first question mark,
       
  2072         // this is to capture the query string
       
  2073         params = url.split( /\?(.+)?/ );
       
  2074 
       
  2075         // rebuild url with callback
       
  2076         url = params[ 0 ] + "?";
       
  2077         if ( params[ 1 ] ) {
       
  2078           url += params[ 1 ] + "&";
       
  2079         }
       
  2080         url += "callback=" + callback;
       
  2081       }
       
  2082 
       
  2083       //  Define the JSONP success callback globally
       
  2084       window[ callback ] = function( data ) {
       
  2085         // Fire success callbacks
       
  2086         success && success( data );
       
  2087         isFired = true;
       
  2088       };
       
  2089     }
       
  2090 
       
  2091     script.addEventListener( "load",  function() {
       
  2092 
       
  2093       //  Handling remote script loading callbacks
       
  2094       if ( isScript ) {
       
  2095         //  getScript
       
  2096         success && success();
       
  2097       }
       
  2098 
       
  2099       //  Executing for JSONP requests
       
  2100       if ( isFired ) {
       
  2101         //  Garbage collect the callback
       
  2102         delete window[ callback ];
       
  2103       }
       
  2104       //  Garbage collect the script resource
       
  2105       head.removeChild( script );
       
  2106     }, false );
       
  2107 
       
  2108     script.src = url;
       
  2109 
       
  2110     head.insertBefore( script, head.firstChild );
       
  2111 
       
  2112     return;
       
  2113   };
       
  2114 
       
  2115   Popcorn.getJSONP = Popcorn.xhr.getJSONP;
       
  2116 
       
  2117   Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
       
  2118 
       
  2119     return Popcorn.xhr.getJSONP( url, success, true );
       
  2120   };
       
  2121 
       
  2122   Popcorn.util = {
       
  2123     // Simple function to parse a timestamp into seconds
       
  2124     // Acceptable formats are:
       
  2125     // HH:MM:SS.MMM
       
  2126     // HH:MM:SS;FF
       
  2127     // Hours and minutes are optional. They default to 0
       
  2128     toSeconds: function( timeStr, framerate ) {
       
  2129       // Hours and minutes are optional
       
  2130       // Seconds must be specified
       
  2131       // Seconds can be followed by milliseconds OR by the frame information
       
  2132       var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
       
  2133           errorMessage = "Invalid time format",
       
  2134           digitPairs, lastIndex, lastPair, firstPair,
       
  2135           frameInfo, frameTime;
       
  2136 
       
  2137       if ( typeof timeStr === "number" ) {
       
  2138         return timeStr;
       
  2139       }
       
  2140 
       
  2141       if ( typeof timeStr === "string" &&
       
  2142             !validTimeFormat.test( timeStr ) ) {
       
  2143         Popcorn.error( errorMessage );
       
  2144       }
       
  2145 
       
  2146       digitPairs = timeStr.split( ":" );
       
  2147       lastIndex = digitPairs.length - 1;
       
  2148       lastPair = digitPairs[ lastIndex ];
       
  2149 
       
  2150       // Fix last element:
       
  2151       if ( lastPair.indexOf( ";" ) > -1 ) {
       
  2152 
       
  2153         frameInfo = lastPair.split( ";" );
       
  2154         frameTime = 0;
       
  2155 
       
  2156         if ( framerate && ( typeof framerate === "number" ) ) {
       
  2157           frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
       
  2158         }
       
  2159 
       
  2160         digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime;
       
  2161       }
       
  2162 
       
  2163       firstPair = digitPairs[ 0 ];
       
  2164 
       
  2165       return {
       
  2166 
       
  2167         1: parseFloat( firstPair, 10 ),
       
  2168 
       
  2169         2: ( parseInt( firstPair, 10 ) * 60 ) +
       
  2170               parseFloat( digitPairs[ 1 ], 10 ),
       
  2171 
       
  2172         3: ( parseInt( firstPair, 10 ) * 3600 ) +
       
  2173             ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) +
       
  2174               parseFloat( digitPairs[ 2 ], 10 )
       
  2175 
       
  2176       }[ digitPairs.length || 1 ];
       
  2177     }
       
  2178   };
       
  2179 
       
  2180   // alias for exec function
       
  2181   Popcorn.p.cue = Popcorn.p.exec;
       
  2182 
       
  2183   //  Protected API methods
       
  2184   Popcorn.protect = {
       
  2185     natives: getKeys( Popcorn.p ).map(function( val ) {
       
  2186       return val.toLowerCase();
       
  2187     })
       
  2188   };
       
  2189 
       
  2190   // Setup logging for deprecated methods
       
  2191   Popcorn.forEach({
       
  2192     // Deprecated: Recommended
       
  2193     "listen": "on",
       
  2194     "unlisten": "off",
       
  2195     "trigger": "emit",
       
  2196     "exec": "cue"
       
  2197 
       
  2198   }, function( recommend, api ) {
       
  2199     var original = Popcorn.p[ api ];
       
  2200     // Override the deprecated api method with a method of the same name
       
  2201     // that logs a warning and defers to the new recommended method
       
  2202     Popcorn.p[ api ] = function() {
       
  2203       if ( typeof console !== "undefined" && console.warn ) {
       
  2204         console.warn(
       
  2205           "Deprecated method '" + api + "', " +
       
  2206           (recommend == null ? "do not use." : "use '" + recommend + "' instead." )
       
  2207         );
       
  2208 
       
  2209         // Restore api after first warning
       
  2210         Popcorn.p[ api ] = original;
       
  2211       }
       
  2212       return Popcorn.p[ recommend ].apply( this, [].slice.call( arguments ) );
       
  2213     };
       
  2214   });
       
  2215 
       
  2216 
       
  2217   //  Exposes Popcorn to global context
       
  2218   global.Popcorn = Popcorn;
       
  2219 
       
  2220 })(window, window.document);
       
  2221 /*!
       
  2222  * Popcorn.sequence
       
  2223  *
       
  2224  * Copyright 2011, Rick Waldron
       
  2225  * Licensed under MIT license.
       
  2226  *
       
  2227  */
       
  2228 
       
  2229 /* jslint forin: true, maxerr: 50, indent: 4, es5: true  */
       
  2230 /* global Popcorn: true */
       
  2231 
       
  2232 // Requires Popcorn.js
       
  2233 (function( global, Popcorn ) {
       
  2234 
       
  2235   // TODO: as support increases, migrate to element.dataset
       
  2236   var doc = global.document,
       
  2237       location = global.location,
       
  2238       rprotocol = /:\/\//,
       
  2239       // TODO: better solution to this sucky stop-gap
       
  2240       lochref = location.href.replace( location.href.split("/").slice(-1)[0], "" ),
       
  2241       // privately held
       
  2242       range = function(start, stop, step) {
       
  2243 
       
  2244         start = start || 0;
       
  2245         stop = ( stop || start || 0 ) + 1;
       
  2246         step = step || 1;
       
  2247 
       
  2248         var len = Math.ceil((stop - start) / step) || 0,
       
  2249             idx = 0,
       
  2250             range = [];
       
  2251 
       
  2252         range.length = len;
       
  2253 
       
  2254         while (idx < len) {
       
  2255          range[idx++] = start;
       
  2256          start += step;
       
  2257         }
       
  2258         return range;
       
  2259       };
       
  2260 
       
  2261   Popcorn.sequence = function( parent, list ) {
       
  2262     return new Popcorn.sequence.init( parent, list );
       
  2263   };
       
  2264 
       
  2265   Popcorn.sequence.init = function( parent, list ) {
       
  2266 
       
  2267     // Video element
       
  2268     this.parent = doc.getElementById( parent );
       
  2269 
       
  2270     // Store ref to a special ID
       
  2271     this.seqId = Popcorn.guid( "__sequenced" );
       
  2272 
       
  2273     // List of HTMLVideoElements
       
  2274     this.queue = [];
       
  2275 
       
  2276     // List of Popcorn objects
       
  2277     this.playlist = [];
       
  2278 
       
  2279     // Lists of in/out points
       
  2280     this.inOuts = {
       
  2281 
       
  2282       // Stores the video in/out times for each video in sequence
       
  2283       ofVideos: [],
       
  2284 
       
  2285       // Stores the clip in/out times for each clip in sequences
       
  2286       ofClips: []
       
  2287 
       
  2288     };
       
  2289 
       
  2290     // Store first video dimensions
       
  2291     this.dims = {
       
  2292       width: 0, //this.video.videoWidth,
       
  2293       height: 0 //this.video.videoHeight
       
  2294     };
       
  2295 
       
  2296     this.active = 0;
       
  2297     this.cycling = false;
       
  2298     this.playing = false;
       
  2299 
       
  2300     this.times = {
       
  2301       last: 0
       
  2302     };
       
  2303 
       
  2304     // Store event pointers and queues
       
  2305     this.events = {
       
  2306 
       
  2307     };
       
  2308 
       
  2309     var self = this,
       
  2310         clipOffset = 0;
       
  2311 
       
  2312     // Create `video` elements
       
  2313     Popcorn.forEach( list, function( media, idx ) {
       
  2314 
       
  2315       var video = doc.createElement( "video" );
       
  2316 
       
  2317       video.preload = "auto";
       
  2318 
       
  2319       // Setup newly created video element
       
  2320       video.controls = true;
       
  2321 
       
  2322       // If the first, show it, if the after, hide it
       
  2323       video.style.display = ( idx && "none" ) || "" ;
       
  2324 
       
  2325       // Seta registered sequence id
       
  2326       video.id = self.seqId + "-" + idx ;
       
  2327 
       
  2328       // Push this video into the sequence queue
       
  2329       self.queue.push( video );
       
  2330 
       
  2331       var //satisfy lint
       
  2332        mIn = media["in"],
       
  2333        mOut = media["out"];
       
  2334 
       
  2335       // Push the in/out points into sequence ioVideos
       
  2336       self.inOuts.ofVideos.push({
       
  2337         "in": ( mIn !== undefined && mIn ) || 1,
       
  2338         "out": ( mOut !== undefined && mOut ) || 0
       
  2339       });
       
  2340 
       
  2341       self.inOuts.ofVideos[ idx ]["out"] = self.inOuts.ofVideos[ idx ]["out"] || self.inOuts.ofVideos[ idx ]["in"] + 2;
       
  2342 
       
  2343       // Set the sources
       
  2344       video.src = !rprotocol.test( media.src ) ? lochref + media.src : media.src;
       
  2345 
       
  2346       // Set some squence specific data vars
       
  2347       video.setAttribute("data-sequence-owner", parent );
       
  2348       video.setAttribute("data-sequence-guid", self.seqId );
       
  2349       video.setAttribute("data-sequence-id", idx );
       
  2350       video.setAttribute("data-sequence-clip", [ self.inOuts.ofVideos[ idx ]["in"], self.inOuts.ofVideos[ idx ]["out"] ].join(":") );
       
  2351 
       
  2352       // Append the video to the parent element
       
  2353       self.parent.appendChild( video );
       
  2354 
       
  2355 
       
  2356       self.playlist.push( Popcorn("#" + video.id ) );
       
  2357 
       
  2358     });
       
  2359 
       
  2360     self.inOuts.ofVideos.forEach(function( obj ) {
       
  2361 
       
  2362       var clipDuration = obj["out"] - obj["in"],
       
  2363           offs = {
       
  2364             "in": clipOffset,
       
  2365             "out": clipOffset + clipDuration
       
  2366           };
       
  2367 
       
  2368       self.inOuts.ofClips.push( offs );
       
  2369 
       
  2370       clipOffset = offs["out"] + 1;
       
  2371     });
       
  2372 
       
  2373     Popcorn.forEach( this.queue, function( media, idx ) {
       
  2374 
       
  2375       function canPlayThrough( event ) {
       
  2376 
       
  2377         // If this is idx zero, use it as dimension for all
       
  2378         if ( !idx ) {
       
  2379           self.dims.width = media.videoWidth;
       
  2380           self.dims.height = media.videoHeight;
       
  2381         }
       
  2382 
       
  2383         media.currentTime = self.inOuts.ofVideos[ idx ]["in"] - 0.5;
       
  2384 
       
  2385         media.removeEventListener( "canplaythrough", canPlayThrough, false );
       
  2386 
       
  2387         return true;
       
  2388       }
       
  2389 
       
  2390       // Hook up event listeners for managing special playback
       
  2391       media.addEventListener( "canplaythrough", canPlayThrough, false );
       
  2392 
       
  2393       // TODO: consolidate & DRY
       
  2394       media.addEventListener( "play", function( event ) {
       
  2395 
       
  2396         self.playing = true;
       
  2397 
       
  2398       }, false );
       
  2399 
       
  2400       media.addEventListener( "pause", function( event ) {
       
  2401 
       
  2402         self.playing = false;
       
  2403 
       
  2404       }, false );
       
  2405 
       
  2406       media.addEventListener( "timeupdate", function( event ) {
       
  2407 
       
  2408         var target = event.srcElement || event.target,
       
  2409             seqIdx = +(  (target.dataset && target.dataset.sequenceId) || target.getAttribute("data-sequence-id") ),
       
  2410             floor = Math.floor( media.currentTime );
       
  2411 
       
  2412         if ( self.times.last !== floor &&
       
  2413               seqIdx === self.active ) {
       
  2414 
       
  2415           self.times.last = floor;
       
  2416 
       
  2417           if ( floor === self.inOuts.ofVideos[ seqIdx ]["out"] ) {
       
  2418 
       
  2419             Popcorn.sequence.cycle.call( self, seqIdx );
       
  2420           }
       
  2421         }
       
  2422       }, false );
       
  2423     });
       
  2424 
       
  2425     return this;
       
  2426   };
       
  2427 
       
  2428   Popcorn.sequence.init.prototype = Popcorn.sequence.prototype;
       
  2429 
       
  2430   //
       
  2431   Popcorn.sequence.cycle = function( idx ) {
       
  2432 
       
  2433     if ( !this.queue ) {
       
  2434       Popcorn.error("Popcorn.sequence.cycle is not a public method");
       
  2435     }
       
  2436 
       
  2437     var // Localize references
       
  2438     queue = this.queue,
       
  2439     ioVideos = this.inOuts.ofVideos,
       
  2440     current = queue[ idx ],
       
  2441     nextIdx = 0,
       
  2442     next, clip;
       
  2443 
       
  2444 
       
  2445     var // Popcorn instances
       
  2446     $popnext,
       
  2447     $popprev;
       
  2448 
       
  2449 
       
  2450     if ( queue[ idx + 1 ] ) {
       
  2451       nextIdx = idx + 1;
       
  2452     }
       
  2453 
       
  2454     // Reset queue
       
  2455     if ( !queue[ idx + 1 ] ) {
       
  2456 
       
  2457       nextIdx = 0;
       
  2458       this.playlist[ idx ].pause();
       
  2459 
       
  2460     } else {
       
  2461 
       
  2462       next = queue[ nextIdx ];
       
  2463       clip = ioVideos[ nextIdx ];
       
  2464 
       
  2465       // Constrain dimentions
       
  2466       Popcorn.extend( next, {
       
  2467         width: this.dims.width,
       
  2468         height: this.dims.height
       
  2469       });
       
  2470 
       
  2471       $popnext = this.playlist[ nextIdx ];
       
  2472       $popprev = this.playlist[ idx ];
       
  2473 
       
  2474       // When not resetting to 0
       
  2475       current.pause();
       
  2476 
       
  2477       this.active = nextIdx;
       
  2478       this.times.last = clip["in"] - 1;
       
  2479 
       
  2480       // Play the next video in the sequence
       
  2481       $popnext.currentTime( clip["in"] );
       
  2482 
       
  2483       $popnext[ nextIdx ? "play" : "pause" ]();
       
  2484 
       
  2485       // Trigger custom cycling event hook
       
  2486       this.trigger( "cycle", {
       
  2487 
       
  2488         position: {
       
  2489           previous: idx,
       
  2490           current: nextIdx
       
  2491         }
       
  2492 
       
  2493       });
       
  2494 
       
  2495       // Set the previous back to it's beginning time
       
  2496       // $popprev.currentTime( ioVideos[ idx ].in );
       
  2497 
       
  2498       if ( nextIdx ) {
       
  2499         // Hide the currently ending video
       
  2500         current.style.display = "none";
       
  2501         // Show the next video in the sequence
       
  2502         next.style.display = "";
       
  2503       }
       
  2504 
       
  2505       this.cycling = false;
       
  2506     }
       
  2507 
       
  2508     return this;
       
  2509   };
       
  2510 
       
  2511   var excludes = [ "timeupdate", "play", "pause" ];
       
  2512 
       
  2513   // Sequence object prototype
       
  2514   Popcorn.extend( Popcorn.sequence.prototype, {
       
  2515 
       
  2516     // Returns Popcorn object from sequence at index
       
  2517     eq: function( idx ) {
       
  2518       return this.playlist[ idx ];
       
  2519     },
       
  2520     // Remove a sequence from it's playback display container
       
  2521     remove: function() {
       
  2522       this.parent.innerHTML = null;
       
  2523     },
       
  2524     // Returns Clip object from sequence at index
       
  2525     clip: function( idx ) {
       
  2526       return this.inOuts.ofVideos[ idx ];
       
  2527     },
       
  2528     // Returns sum duration for all videos in sequence
       
  2529     duration: function() {
       
  2530 
       
  2531       var ret = 0,
       
  2532           seq = this.inOuts.ofClips,
       
  2533           idx = 0;
       
  2534 
       
  2535       for ( ; idx < seq.length; idx++ ) {
       
  2536         ret += seq[ idx ]["out"] - seq[ idx ]["in"] + 1;
       
  2537       }
       
  2538 
       
  2539       return ret - 1;
       
  2540     },
       
  2541 
       
  2542     play: function() {
       
  2543 
       
  2544       this.playlist[ this.active ].play();
       
  2545 
       
  2546       return this;
       
  2547     },
       
  2548     // Attach an event to a single point in time
       
  2549     exec: function ( time, fn ) {
       
  2550 
       
  2551       var index = this.active;
       
  2552 
       
  2553       this.inOuts.ofClips.forEach(function( off, idx ) {
       
  2554         if ( time >= off["in"] && time <= off["out"] ) {
       
  2555           index = idx;
       
  2556         }
       
  2557       });
       
  2558 
       
  2559       //offsetBy = time - self.inOuts.ofVideos[ index ].in;
       
  2560 
       
  2561       time += this.inOuts.ofVideos[ index ]["in"] - this.inOuts.ofClips[ index ]["in"];
       
  2562 
       
  2563       // Creating a one second track event with an empty end
       
  2564       Popcorn.addTrackEvent( this.playlist[ index ], {
       
  2565         start: time - 1,
       
  2566         end: time,
       
  2567         _running: false,
       
  2568         _natives: {
       
  2569           start: fn || Popcorn.nop,
       
  2570           end: Popcorn.nop,
       
  2571           type: "exec"
       
  2572         }
       
  2573       });
       
  2574 
       
  2575       return this;
       
  2576     },
       
  2577     // Binds event handlers that fire only when all
       
  2578     // videos in sequence have heard the event
       
  2579     listen: function( type, callback ) {
       
  2580 
       
  2581       var self = this,
       
  2582           seq = this.playlist,
       
  2583           total = seq.length,
       
  2584           count = 0,
       
  2585           fnName;
       
  2586 
       
  2587       if ( !callback ) {
       
  2588         callback = Popcorn.nop;
       
  2589       }
       
  2590 
       
  2591       // Handling for DOM and Media events
       
  2592       if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
       
  2593         Popcorn.forEach( seq, function( video ) {
       
  2594 
       
  2595           video.listen( type, function( event ) {
       
  2596 
       
  2597             event.active = self;
       
  2598 
       
  2599             if ( excludes.indexOf( type ) > -1 ) {
       
  2600 
       
  2601               callback.call( video, event );
       
  2602 
       
  2603             } else {
       
  2604               if ( ++count === total ) {
       
  2605                 callback.call( video, event );
       
  2606               }
       
  2607             }
       
  2608           });
       
  2609         });
       
  2610 
       
  2611       } else {
       
  2612 
       
  2613         // If no events registered with this name, create a cache
       
  2614         if ( !this.events[ type ] ) {
       
  2615           this.events[ type ] = {};
       
  2616         }
       
  2617 
       
  2618         // Normalize a callback name key
       
  2619         fnName = callback.name || Popcorn.guid( "__" + type );
       
  2620 
       
  2621         // Store in event cache
       
  2622         this.events[ type ][ fnName ] = callback;
       
  2623       }
       
  2624 
       
  2625       // Return the sequence object
       
  2626       return this;
       
  2627     },
       
  2628     unlisten: function( type, name ) {
       
  2629       // TODO: finish implementation
       
  2630     },
       
  2631     trigger: function( type, data ) {
       
  2632       var self = this;
       
  2633 
       
  2634       // Handling for DOM and Media events
       
  2635       if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) {
       
  2636 
       
  2637         //  find the active video and trigger api events on that video.
       
  2638         return;
       
  2639 
       
  2640       } else {
       
  2641 
       
  2642         // Only proceed if there are events of this type
       
  2643         // currently registered on the sequence
       
  2644         if ( this.events[ type ] ) {
       
  2645 
       
  2646           Popcorn.forEach( this.events[ type ], function( callback, name ) {
       
  2647             callback.call( self, { type: type }, data );
       
  2648           });
       
  2649 
       
  2650         }
       
  2651       }
       
  2652 
       
  2653       return this;
       
  2654     }
       
  2655   });
       
  2656 
       
  2657 
       
  2658   Popcorn.forEach( Popcorn.manifest, function( obj, plugin ) {
       
  2659 
       
  2660     // Implement passthrough methods to plugins
       
  2661     Popcorn.sequence.prototype[ plugin ] = function( options ) {
       
  2662 
       
  2663       // console.log( this, options );
       
  2664       var videos = {}, assignTo = [],
       
  2665       idx, off, inOuts, inIdx, outIdx, keys, clip, clipInOut, clipRange;
       
  2666 
       
  2667       for ( idx = 0; idx < this.inOuts.ofClips.length; idx++  ) {
       
  2668         // store reference
       
  2669         off = this.inOuts.ofClips[ idx ];
       
  2670         // array to test against
       
  2671         inOuts = range( off["in"], off["out"] );
       
  2672 
       
  2673         inIdx = inOuts.indexOf( options.start );
       
  2674         outIdx = inOuts.indexOf( options.end );
       
  2675 
       
  2676         if ( inIdx > -1 ) {
       
  2677           videos[ idx ] = Popcorn.extend( {}, off, {
       
  2678             start: inOuts[ inIdx ],
       
  2679             clipIdx: inIdx
       
  2680           });
       
  2681         }
       
  2682 
       
  2683         if ( outIdx > -1 ) {
       
  2684           videos[ idx ] = Popcorn.extend( {}, off, {
       
  2685             end: inOuts[ outIdx ],
       
  2686             clipIdx: outIdx
       
  2687           });
       
  2688         }
       
  2689       }
       
  2690 
       
  2691       keys = Object.keys( videos ).map(function( val ) {
       
  2692                 return +val;
       
  2693               });
       
  2694 
       
  2695       assignTo = range( keys[ 0 ], keys[ 1 ] );
       
  2696 
       
  2697       //console.log( "PLUGIN CALL MAPS: ", videos, keys, assignTo );
       
  2698       for ( idx = 0; idx < assignTo.length; idx++ ) {
       
  2699 
       
  2700         var compile = {},
       
  2701         play = assignTo[ idx ],
       
  2702         vClip = videos[ play ];
       
  2703 
       
  2704         if ( vClip ) {
       
  2705 
       
  2706           // has instructions
       
  2707           clip = this.inOuts.ofVideos[ play ];
       
  2708           clipInOut = vClip.clipIdx;
       
  2709           clipRange = range( clip["in"], clip["out"] );
       
  2710 
       
  2711           if ( vClip.start ) {
       
  2712             compile.start = clipRange[ clipInOut ];
       
  2713             compile.end = clipRange[ clipRange.length - 1 ];
       
  2714           }
       
  2715 
       
  2716           if ( vClip.end ) {
       
  2717             compile.start = clipRange[ 0 ];
       
  2718             compile.end = clipRange[ clipInOut ];
       
  2719           }
       
  2720 
       
  2721           //compile.start += 0.1;
       
  2722           //compile.end += 0.9;
       
  2723 
       
  2724         } else {
       
  2725 
       
  2726           compile.start = this.inOuts.ofVideos[ play ]["in"];
       
  2727           compile.end = this.inOuts.ofVideos[ play ]["out"];
       
  2728 
       
  2729           //compile.start += 0.1;
       
  2730           //compile.end += 0.9;
       
  2731 
       
  2732         }
       
  2733 
       
  2734         // Handling full clip persistance
       
  2735         //if ( compile.start === compile.end ) {
       
  2736           //compile.start -= 0.1;
       
  2737           //compile.end += 0.9;
       
  2738         //}
       
  2739 
       
  2740         // Call the plugin on the appropriate Popcorn object in the playlist
       
  2741         // Merge original options object & compiled (start/end) object into
       
  2742         // a new fresh object
       
  2743         this.playlist[ play ][ plugin ](
       
  2744 
       
  2745           Popcorn.extend( {}, options, compile )
       
  2746 
       
  2747         );
       
  2748 
       
  2749       }
       
  2750 
       
  2751       // Return the sequence object
       
  2752       return this;
       
  2753     };
       
  2754 
       
  2755   });
       
  2756 })( this, Popcorn );
       
  2757 (function( Popcorn ) {
       
  2758   document.addEventListener( "DOMContentLoaded", function() {
       
  2759 
       
  2760     //  Supports non-specific elements
       
  2761     var dataAttr = "data-timeline-sources",
       
  2762         medias = document.querySelectorAll( "[" + dataAttr + "]" );
       
  2763 
       
  2764     Popcorn.forEach( medias, function( idx, key ) {
       
  2765 
       
  2766       var media = medias[ key ],
       
  2767           hasDataSources = false,
       
  2768           dataSources, data, popcornMedia;
       
  2769 
       
  2770       //  Ensure that the DOM has an id
       
  2771       if ( !media.id ) {
       
  2772 
       
  2773         media.id = Popcorn.guid( "__popcorn" );
       
  2774       }
       
  2775 
       
  2776       //  Ensure we're looking at a dom node
       
  2777       if ( media.nodeType && media.nodeType === 1 ) {
       
  2778 
       
  2779         popcornMedia = Popcorn( "#" + media.id );
       
  2780 
       
  2781         dataSources = ( media.getAttribute( dataAttr ) || "" ).split( "," );
       
  2782 
       
  2783         if ( dataSources[ 0 ] ) {
       
  2784 
       
  2785           Popcorn.forEach( dataSources, function( source ) {
       
  2786 
       
  2787             // split the parser and data as parser!file
       
  2788             data = source.split( "!" );
       
  2789 
       
  2790             // if no parser is defined for the file, assume "parse" + file extension
       
  2791             if ( data.length === 1 ) {
       
  2792 
       
  2793               // parse a relative URL for the filename, split to get extension
       
  2794               data = source.match( /(.*)[\/\\]([^\/\\]+\.\w+)$/ )[ 2 ].split( "." );
       
  2795 
       
  2796               data[ 0 ] = "parse" + data[ 1 ].toUpperCase();
       
  2797               data[ 1 ] = source;
       
  2798             }
       
  2799 
       
  2800             //  If the media has data sources and the correct parser is registered, continue to load
       
  2801             if ( dataSources[ 0 ] && popcornMedia[ data[ 0 ] ] ) {
       
  2802 
       
  2803               //  Set up the media and load in the datasources
       
  2804               popcornMedia[ data[ 0 ] ]( data[ 1 ] );
       
  2805 
       
  2806             }
       
  2807           });
       
  2808 
       
  2809         }
       
  2810 
       
  2811         //  Only play the media if it was specified to do so
       
  2812         if ( !!popcornMedia.autoplay() ) {
       
  2813           popcornMedia.play();
       
  2814         }
       
  2815 
       
  2816       }
       
  2817     });
       
  2818   }, false );
       
  2819 
       
  2820 })( Popcorn );(function( global, Popcorn ) {
       
  2821 
       
  2822   var navigator = global.navigator;
       
  2823 
       
  2824   // Initialize locale data
       
  2825   // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes
       
  2826   function initLocale( arg ) {
       
  2827 
       
  2828     var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ),
       
  2829         parts = locale.split( "-" );
       
  2830 
       
  2831     // Setup locale data table
       
  2832     return {
       
  2833       iso6391: locale,
       
  2834       language: parts[ 0 ] || "",
       
  2835       region: parts[ 1 ] || ""
       
  2836     };
       
  2837   }
       
  2838 
       
  2839   // Declare locale data table
       
  2840   var localeData = initLocale( navigator.userLanguage || navigator.language );
       
  2841 
       
  2842   Popcorn.locale = {
       
  2843 
       
  2844     // Popcorn.locale.get()
       
  2845     // returns reference to privately
       
  2846     // defined localeData
       
  2847     get: function() {
       
  2848       return localeData;
       
  2849     },
       
  2850 
       
  2851     // Popcorn.locale.set( string|object );
       
  2852     set: function( arg ) {
       
  2853 
       
  2854       localeData = initLocale( arg );
       
  2855 
       
  2856       Popcorn.locale.broadcast();
       
  2857 
       
  2858       return localeData;
       
  2859     },
       
  2860 
       
  2861     // Popcorn.locale.broadcast( type )
       
  2862     // Sends events to all popcorn media instances that are
       
  2863     // listening for locale events
       
  2864     broadcast: function( type ) {
       
  2865 
       
  2866       var instances = Popcorn.instances,
       
  2867           length = instances.length,
       
  2868           idx = 0,
       
  2869           instance;
       
  2870 
       
  2871       type = type || "locale:changed";
       
  2872 
       
  2873       // Iterate all current instances
       
  2874       for ( ; idx < length; idx++ ) {
       
  2875         instance = instances[ idx ];
       
  2876 
       
  2877         // For those instances with locale event listeners,
       
  2878         // trigger a locale change event
       
  2879         if ( type in instance.data.events  ) {
       
  2880           instance.trigger( type );
       
  2881         }
       
  2882       }
       
  2883     }
       
  2884   };
       
  2885 })( this, this.Popcorn );(function( Popcorn ) {
       
  2886 
       
  2887   var
       
  2888 
       
  2889   AP = Array.prototype,
       
  2890   OP = Object.prototype,
       
  2891 
       
  2892   forEach = AP.forEach,
       
  2893   slice = AP.slice,
       
  2894   hasOwn = OP.hasOwnProperty,
       
  2895   toString = OP.toString;
       
  2896 
       
  2897   // stores parsers keyed on filetype
       
  2898   Popcorn.parsers = {};
       
  2899 
       
  2900   // An interface for extending Popcorn
       
  2901   // with parser functionality
       
  2902   Popcorn.parser = function( name, type, definition ) {
       
  2903 
       
  2904     if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
       
  2905       Popcorn.error( "'" + name + "' is a protected function name" );
       
  2906       return;
       
  2907     }
       
  2908 
       
  2909     // fixes parameters for overloaded function call
       
  2910     if ( typeof type === "function" && !definition ) {
       
  2911       definition = type;
       
  2912       type = "";
       
  2913     }
       
  2914 
       
  2915     if ( typeof definition !== "function" || typeof type !== "string" ) {
       
  2916       return;
       
  2917     }
       
  2918 
       
  2919     // Provides some sugar, but ultimately extends
       
  2920     // the definition into Popcorn.p
       
  2921 
       
  2922     var natives = Popcorn.events.all,
       
  2923         parseFn,
       
  2924         parser = {};
       
  2925 
       
  2926     parseFn = function( filename, callback ) {
       
  2927 
       
  2928       if ( !filename ) {
       
  2929         return this;
       
  2930       }
       
  2931 
       
  2932       var that = this;
       
  2933 
       
  2934       Popcorn.xhr({
       
  2935         url: filename,
       
  2936         dataType: type,
       
  2937         success: function( data ) {
       
  2938 
       
  2939           var tracksObject = definition( data ),
       
  2940               tracksData,
       
  2941               tracksDataLen,
       
  2942               tracksDef,
       
  2943               idx = 0;
       
  2944 
       
  2945           tracksData = tracksObject.data || [];
       
  2946           tracksDataLen = tracksData.length;
       
  2947           tracksDef = null;
       
  2948 
       
  2949           //  If no tracks to process, return immediately
       
  2950           if ( !tracksDataLen ) {
       
  2951             return;
       
  2952           }
       
  2953 
       
  2954           //  Create tracks out of parsed object
       
  2955           for ( ; idx < tracksDataLen; idx++ ) {
       
  2956 
       
  2957             tracksDef = tracksData[ idx ];
       
  2958 
       
  2959             for ( var key in tracksDef ) {
       
  2960 
       
  2961               if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
       
  2962 
       
  2963                 that[ key ]( tracksDef[ key ] );
       
  2964               }
       
  2965             }
       
  2966           }
       
  2967           if ( callback ) {
       
  2968             callback();
       
  2969           }
       
  2970         }
       
  2971       });
       
  2972 
       
  2973       return this;
       
  2974     };
       
  2975 
       
  2976     // Assign new named definition
       
  2977     parser[ name ] = parseFn;
       
  2978 
       
  2979     // Extend Popcorn.p with new named definition
       
  2980     Popcorn.extend( Popcorn.p, parser );
       
  2981 
       
  2982     // keys the function name by filetype extension
       
  2983     //Popcorn.parsers[ name ] = true;
       
  2984 
       
  2985     return parser;
       
  2986   };
       
  2987 })( Popcorn );(function( Popcorn ) {
       
  2988 
       
  2989   // combines calls of two function calls into one
       
  2990   var combineFn = function( first, second ) {
       
  2991 
       
  2992     first = first || Popcorn.nop;
       
  2993     second = second || Popcorn.nop;
       
  2994 
       
  2995     return function() {
       
  2996 
       
  2997       first.apply( this, arguments );
       
  2998       second.apply( this, arguments );
       
  2999     };
       
  3000   };
       
  3001 
       
  3002   //  ID string matching
       
  3003   var rIdExp  = /^(#([\w\-\_\.]+))$/;
       
  3004 
       
  3005   var audioExtensions = "ogg|oga|aac|mp3|wav",
       
  3006       videoExtensions = "ogg|ogv|mp4|webm",
       
  3007       mediaExtensions = audioExtensions + "|" + videoExtensions;
       
  3008 
       
  3009   var audioExtensionRegexp = new RegExp( "^.*\\.(" + audioExtensions + ")($|\\?)" ),
       
  3010       mediaExtensionRegexp = new RegExp( "^.*\\.(" + mediaExtensions + ")($|\\?)" );
       
  3011 
       
  3012   Popcorn.player = function( name, player ) {
       
  3013 
       
  3014     // return early if a player already exists under this name
       
  3015     if ( Popcorn[ name ] ) {
       
  3016 
       
  3017       return;
       
  3018     }
       
  3019 
       
  3020     player = player || {};
       
  3021 
       
  3022     var playerFn = function( target, src, options ) {
       
  3023 
       
  3024       options = options || {};
       
  3025 
       
  3026       // List of events
       
  3027       var date = new Date() / 1000,
       
  3028           baselineTime = date,
       
  3029           currentTime = 0,
       
  3030           readyState = 0,
       
  3031           volume = 1,
       
  3032           muted = false,
       
  3033           events = {},
       
  3034 
       
  3035           // The container div of the resource
       
  3036           container = typeof target === "string" ? Popcorn.dom.find( target ) : target,
       
  3037           basePlayer = {},
       
  3038           timeout,
       
  3039           popcorn;
       
  3040 
       
  3041       if ( !Object.prototype.__defineGetter__ ) {
       
  3042 
       
  3043         basePlayer = container || document.createElement( "div" );
       
  3044       }
       
  3045 
       
  3046       // copies a div into the media object
       
  3047       for( var val in container ) {
       
  3048 
       
  3049         // don't copy properties if using container as baseplayer
       
  3050         if ( val in basePlayer ) {
       
  3051 
       
  3052           continue;
       
  3053         }
       
  3054 
       
  3055         if ( typeof container[ val ] === "object" ) {
       
  3056 
       
  3057           basePlayer[ val ] = container[ val ];
       
  3058         } else if ( typeof container[ val ] === "function" ) {
       
  3059 
       
  3060           basePlayer[ val ] = (function( value ) {
       
  3061 
       
  3062             // this is a stupid ugly kludgy hack in honour of Safari
       
  3063             // in Safari a NodeList is a function, not an object
       
  3064             if ( "length" in container[ value ] && !container[ value ].call ) {
       
  3065 
       
  3066               return container[ value ];
       
  3067             } else {
       
  3068 
       
  3069               return function() {
       
  3070 
       
  3071                 return container[ value ].apply( container, arguments );
       
  3072               };
       
  3073             }
       
  3074           }( val ));
       
  3075         } else {
       
  3076 
       
  3077           Popcorn.player.defineProperty( basePlayer, val, {
       
  3078             get: (function( value ) {
       
  3079 
       
  3080               return function() {
       
  3081 
       
  3082                 return container[ value ];
       
  3083               };
       
  3084             }( val )),
       
  3085             set: Popcorn.nop,
       
  3086             configurable: true
       
  3087           });
       
  3088         }
       
  3089       }
       
  3090 
       
  3091       var timeupdate = function() {
       
  3092 
       
  3093         date = new Date() / 1000;
       
  3094 
       
  3095         if ( !basePlayer.paused ) {
       
  3096 
       
  3097           basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime );
       
  3098           basePlayer.dispatchEvent( "timeupdate" );
       
  3099           timeout = setTimeout( timeupdate, 10 );
       
  3100         }
       
  3101 
       
  3102         baselineTime = date;
       
  3103       };
       
  3104 
       
  3105       basePlayer.play = function() {
       
  3106 
       
  3107         this.paused = false;
       
  3108 
       
  3109         if ( basePlayer.readyState >= 4 ) {
       
  3110 
       
  3111           baselineTime = new Date() / 1000;
       
  3112           basePlayer.dispatchEvent( "play" );
       
  3113           timeupdate();
       
  3114         }
       
  3115       };
       
  3116 
       
  3117       basePlayer.pause = function() {
       
  3118 
       
  3119         this.paused = true;
       
  3120         basePlayer.dispatchEvent( "pause" );
       
  3121       };
       
  3122 
       
  3123       Popcorn.player.defineProperty( basePlayer, "currentTime", {
       
  3124         get: function() {
       
  3125 
       
  3126           return currentTime;
       
  3127         },
       
  3128         set: function( val ) {
       
  3129 
       
  3130           // make sure val is a number
       
  3131           currentTime = +val;
       
  3132           basePlayer.dispatchEvent( "timeupdate" );
       
  3133 
       
  3134           return currentTime;
       
  3135         },
       
  3136         configurable: true
       
  3137       });
       
  3138 
       
  3139       Popcorn.player.defineProperty( basePlayer, "volume", {
       
  3140         get: function() {
       
  3141 
       
  3142           return volume;
       
  3143         },
       
  3144         set: function( val ) {
       
  3145 
       
  3146           // make sure val is a number
       
  3147           volume = +val;
       
  3148           basePlayer.dispatchEvent( "volumechange" );
       
  3149           return volume;
       
  3150         },
       
  3151         configurable: true
       
  3152       });
       
  3153 
       
  3154       Popcorn.player.defineProperty( basePlayer, "muted", {
       
  3155         get: function() {
       
  3156 
       
  3157           return muted;
       
  3158         },
       
  3159         set: function( val ) {
       
  3160 
       
  3161           // make sure val is a number
       
  3162           muted = +val;
       
  3163           basePlayer.dispatchEvent( "volumechange" );
       
  3164           return muted;
       
  3165         },
       
  3166         configurable: true
       
  3167       });
       
  3168 
       
  3169       Popcorn.player.defineProperty( basePlayer, "readyState", {
       
  3170         get: function() {
       
  3171 
       
  3172           return readyState;
       
  3173         },
       
  3174         set: function( val ) {
       
  3175 
       
  3176           readyState = val;
       
  3177           return readyState;
       
  3178         },
       
  3179         configurable: true
       
  3180       });
       
  3181 
       
  3182       // Adds an event listener to the object
       
  3183       basePlayer.addEventListener = function( evtName, fn ) {
       
  3184 
       
  3185         if ( !events[ evtName ] ) {
       
  3186 
       
  3187           events[ evtName ] = [];
       
  3188         }
       
  3189 
       
  3190         events[ evtName ].push( fn );
       
  3191         return fn;
       
  3192       };
       
  3193 
       
  3194       // Removes an event listener from the object
       
  3195       basePlayer.removeEventListener = function( evtName, fn ) {
       
  3196 
       
  3197         var i,
       
  3198             listeners = events[ evtName ];
       
  3199 
       
  3200         if ( !listeners ){
       
  3201 
       
  3202           return;
       
  3203         }
       
  3204 
       
  3205         // walk backwards so we can safely splice
       
  3206         for ( i = events[ evtName ].length - 1; i >= 0; i-- ) {
       
  3207 
       
  3208           if( fn === listeners[ i ] ) {
       
  3209 
       
  3210             listeners.splice(i, 1);
       
  3211           }
       
  3212         }
       
  3213 
       
  3214         return fn;
       
  3215       };
       
  3216 
       
  3217       // Can take event object or simple string
       
  3218       basePlayer.dispatchEvent = function( oEvent ) {
       
  3219 
       
  3220         var evt,
       
  3221             self = this,
       
  3222             eventInterface,
       
  3223             eventName = oEvent.type;
       
  3224 
       
  3225         // A string was passed, create event object
       
  3226         if ( !eventName ) {
       
  3227 
       
  3228           eventName = oEvent;
       
  3229           eventInterface  = Popcorn.events.getInterface( eventName );
       
  3230 
       
  3231           if ( eventInterface ) {
       
  3232 
       
  3233             evt = document.createEvent( eventInterface );
       
  3234             evt.initEvent( eventName, true, true, window, 1 );
       
  3235           }
       
  3236         }
       
  3237 
       
  3238         if ( events[ eventName ] ) {
       
  3239 
       
  3240           for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) {
       
  3241 
       
  3242             events[ eventName ][ i ].call( self, evt, self );
       
  3243           }
       
  3244         }
       
  3245       };
       
  3246 
       
  3247       // Attempt to get src from playerFn parameter
       
  3248       basePlayer.src = src || "";
       
  3249       basePlayer.duration = 0;
       
  3250       basePlayer.paused = true;
       
  3251       basePlayer.ended = 0;
       
  3252 
       
  3253       options && options.events && Popcorn.forEach( options.events, function( val, key ) {
       
  3254 
       
  3255         basePlayer.addEventListener( key, val, false );
       
  3256       });
       
  3257 
       
  3258       // true and undefined returns on canPlayType means we should attempt to use it,
       
  3259       // false means we cannot play this type
       
  3260       if ( player._canPlayType( container.nodeName, src ) !== false ) {
       
  3261 
       
  3262         if ( player._setup ) {
       
  3263 
       
  3264           player._setup.call( basePlayer, options );
       
  3265         } else {
       
  3266 
       
  3267           // there is no setup, which means there is nothing to load
       
  3268           basePlayer.readyState = 4;
       
  3269           basePlayer.dispatchEvent( "loadedmetadata" );
       
  3270           basePlayer.dispatchEvent( "loadeddata" );
       
  3271           basePlayer.dispatchEvent( "canplaythrough" );
       
  3272         }
       
  3273       } else {
       
  3274 
       
  3275         // Asynchronous so that users can catch this event
       
  3276         setTimeout( function() {
       
  3277           basePlayer.dispatchEvent( "error" );
       
  3278         }, 0 );
       
  3279       }
       
  3280 
       
  3281       popcorn = new Popcorn.p.init( basePlayer, options );
       
  3282 
       
  3283       if ( player._teardown ) {
       
  3284 
       
  3285         popcorn.destroy = combineFn( popcorn.destroy, function() {
       
  3286 
       
  3287           player._teardown.call( basePlayer, options );
       
  3288         });
       
  3289       }
       
  3290 
       
  3291       return popcorn;
       
  3292     };
       
  3293 
       
  3294     playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop;
       
  3295 
       
  3296     Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn;
       
  3297   };
       
  3298 
       
  3299   Popcorn.player.registry = {};
       
  3300 
       
  3301   Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
       
  3302 
       
  3303     object.__defineGetter__( description, options.get || Popcorn.nop );
       
  3304     object.__defineSetter__( description, options.set || Popcorn.nop );
       
  3305   };
       
  3306 
       
  3307   // player queue is to help players queue things like play and pause
       
  3308   // HTML5 video's play and pause are asynch, but do fire in sequence
       
  3309   // play() should really mean "requestPlay()" or "queuePlay()" and
       
  3310   // stash a callback that will play the media resource when it's ready to be played
       
  3311   Popcorn.player.playerQueue = function() {
       
  3312 
       
  3313     var _queue = [],
       
  3314         _running = false;
       
  3315 
       
  3316     return {
       
  3317       next: function() {
       
  3318 
       
  3319         _running = false;
       
  3320         _queue.shift();
       
  3321         _queue[ 0 ] && _queue[ 0 ]();
       
  3322       },
       
  3323       add: function( callback ) {
       
  3324 
       
  3325         _queue.push(function() {
       
  3326 
       
  3327           _running = true;
       
  3328           callback && callback();
       
  3329         });
       
  3330 
       
  3331         // if there is only one item on the queue, start it
       
  3332         !_running && _queue[ 0 ]();
       
  3333       }
       
  3334     };
       
  3335   };
       
  3336 
       
  3337   // smart will attempt to find you a match, if it does not find a match,
       
  3338   // it will attempt to create a video element with the source,
       
  3339   // if that failed, it will throw.
       
  3340   Popcorn.smart = function( target, src, options ) {
       
  3341     var playerType,
       
  3342         elementTypes = [ "AUDIO", "VIDEO" ],
       
  3343         sourceNode,
       
  3344         firstSrc,
       
  3345         node = Popcorn.dom.find( target ),
       
  3346         i, srcResult,
       
  3347         canPlayTypeTester = document.createElement( "video" ),
       
  3348         canPlayTypes = {
       
  3349           "ogg": "video/ogg",
       
  3350           "ogv": "video/ogg",
       
  3351           "oga": "audio/ogg",
       
  3352           "webm": "video/webm",
       
  3353           "mp4": "video/mp4",
       
  3354           "mp3": "audio/mp3"
       
  3355         };
       
  3356 
       
  3357     var canPlayType = function( type ) {
       
  3358 
       
  3359       return canPlayTypeTester.canPlayType( canPlayTypes[ type ] );
       
  3360     };
       
  3361 
       
  3362     var canPlaySrc = function( src ) {
       
  3363 
       
  3364       srcResult = mediaExtensionRegexp.exec( src );
       
  3365 
       
  3366       if ( !srcResult || !srcResult[ 1 ] ) {
       
  3367         return false;
       
  3368       }
       
  3369 
       
  3370       return canPlayType( srcResult[ 1 ] );
       
  3371     };
       
  3372 
       
  3373     if ( !node ) {
       
  3374 
       
  3375       Popcorn.error( "Specified target " + target + " was not found." );
       
  3376       return;
       
  3377     }
       
  3378 
       
  3379     // For when no src is defined.
       
  3380     // Usually this is a video element with a src already on it.
       
  3381     if ( elementTypes.indexOf( node.nodeName ) > -1 && !src ) {
       
  3382 
       
  3383       if ( typeof src === "object" ) {
       
  3384 
       
  3385         options = src;
       
  3386         src = undefined;
       
  3387       }
       
  3388 
       
  3389       return Popcorn( node, options );
       
  3390     }
       
  3391 
       
  3392     // if our src is not an array, create an array of one.	
       
  3393     if ( typeof( src ) === "string" ) {
       
  3394 
       
  3395       src = [ src ];
       
  3396     }
       
  3397 
       
  3398     // go through each src, and find the first playable.
       
  3399     // this only covers player sources popcorn knows of,
       
  3400     // and not things like a youtube src that is private.
       
  3401     // it will still consider a private youtube video to be playable.
       
  3402     for ( i = 0, srcLength = src.length; i < srcLength; i++ ) {
       
  3403 
       
  3404       // src is a playable HTML5 video, we don't need to check custom players.
       
  3405       if ( canPlaySrc( src[ i ] ) ) {
       
  3406 
       
  3407         src = src[ i ];
       
  3408         break;
       
  3409       }
       
  3410 
       
  3411       // for now we loop through and use the first valid player we find.
       
  3412       for ( var key in Popcorn.player.registry ) {
       
  3413 
       
  3414         if ( Popcorn.player.registry.hasOwnProperty( key ) ) {
       
  3415 
       
  3416           if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, src[ i ] ) ) {
       
  3417 
       
  3418             // Popcorn.smart( player, src, /* options */ )
       
  3419             return Popcorn[ key ]( node, src[ i ], options );
       
  3420           }
       
  3421         }
       
  3422       }
       
  3423     }
       
  3424 
       
  3425     // Popcorn.smart( div, src, /* options */ )
       
  3426     // attempting to create a video in a container
       
  3427     if ( elementTypes.indexOf( node.nodeName ) === -1 ) {
       
  3428 
       
  3429       firstSrc = typeof( src ) === "string" ? src : src.length ? src[ 0 ] : src;
       
  3430 
       
  3431       target = document.createElement( !!audioExtensionRegexp.exec( firstSrc ) ? elementTypes[ 0 ] : elementTypes[ 1 ] );
       
  3432 
       
  3433       // Controls are defaulted to being present
       
  3434       target.controls = true;
       
  3435 
       
  3436       node.appendChild( target );
       
  3437       node = target;
       
  3438     }
       
  3439 
       
  3440     options && options.events && options.events.error && node.addEventListener( "error", options.events.error, false );
       
  3441     node.src = src;
       
  3442 
       
  3443     return Popcorn( node, options );
       
  3444 
       
  3445   };
       
  3446 })( Popcorn );
       
  3447 // PLUGIN: mediaspawner
       
  3448 /**
       
  3449   * mediaspawner Popcorn Plugin.
       
  3450   * Adds Video/Audio to the page using Popcorns players
       
  3451   * Start is the time that you want this plug-in to execute
       
  3452   * End is the time that you want this plug-in to stop executing
       
  3453   *
       
  3454   * @param {HTML} options
       
  3455   *
       
  3456   * Example:
       
  3457     var p = Popcorn('#video')
       
  3458       .mediaspawner( {
       
  3459         source: "http://www.youtube.com/watch?v=bUB1L3zGVvc",
       
  3460         target: "mediaspawnerdiv",
       
  3461         start: 1,
       
  3462         end: 10,
       
  3463         caption: "This is a test. We are assuming conrol. We are assuming control."
       
  3464       })
       
  3465   *
       
  3466   */
       
  3467 (function ( Popcorn, global ) {
       
  3468   var PLAYER_URL = "http://popcornjs.org/code/modules/player/popcorn.player.js",
       
  3469       urlRegex = /(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu|vimeo|soundcloud|baseplayer)/,
       
  3470       forEachPlayer,
       
  3471       playerTypeLoading = {},
       
  3472       playerTypesLoaded = {
       
  3473         "vimeo": false,
       
  3474         "youtube": false,
       
  3475         "soundcloud": false,
       
  3476         "module": false
       
  3477       };
       
  3478 
       
  3479   Object.defineProperty( playerTypeLoading, forEachPlayer, {
       
  3480     get: function() {
       
  3481       return playerTypesLoaded[ forEachPlayer ];
       
  3482     },
       
  3483     set: function( val ) {
       
  3484       playerTypesLoaded[ forEachPlayer ] = val;
       
  3485     }
       
  3486   });
       
  3487 
       
  3488   Popcorn.plugin( "mediaspawner", {
       
  3489     manifest: {
       
  3490       about: {
       
  3491         name: "Popcorn Media Spawner Plugin",
       
  3492         version: "0.1",
       
  3493         author: "Matthew Schranz, @mjschranz",
       
  3494         website: "mschranz.wordpress.com"
       
  3495       },
       
  3496       options: {
       
  3497         source: {
       
  3498           elem: "input",
       
  3499           type: "text",
       
  3500           label: "Media Source",
       
  3501           "default": "http://www.youtube.com/watch?v=CXDstfD9eJ0"
       
  3502         },
       
  3503         caption: {
       
  3504           elem: "input",
       
  3505           type: "text",
       
  3506           label: "Media Caption",
       
  3507           "default": "Popcorn Popping",
       
  3508           optional: true
       
  3509         },
       
  3510         target: "mediaspawner-container",
       
  3511         start: {
       
  3512           elem: "input",
       
  3513           type: "number",
       
  3514           label: "Start"
       
  3515         },
       
  3516         end: {
       
  3517           elem: "input",
       
  3518           type: "number",
       
  3519           label: "End"
       
  3520         },
       
  3521         autoplay: {
       
  3522           elem: "input",
       
  3523           type: "checkbox",
       
  3524           label: "Autoplay Video",
       
  3525           optional: true
       
  3526         },
       
  3527         width: {
       
  3528           elem: "input",
       
  3529           type: "number",
       
  3530           label: "Media Width",
       
  3531           "default": 400,
       
  3532           units: "px",
       
  3533           optional: true
       
  3534         },
       
  3535         height: {
       
  3536           elem: "input",
       
  3537           type: "number",
       
  3538           label: "Media Height",
       
  3539           "default": 200,
       
  3540           units: "px",
       
  3541           optional: true
       
  3542         }
       
  3543       }
       
  3544     },
       
  3545     _setup: function( options ) {
       
  3546       var target = document.getElementById( options.target ) || {},
       
  3547           mediaType,
       
  3548           container,
       
  3549           capContainer,
       
  3550           regexResult;
       
  3551 
       
  3552       regexResult = urlRegex.exec( options.source );
       
  3553       if ( regexResult ) {
       
  3554         mediaType = regexResult[ 1 ];
       
  3555         // our regex only handles youtu ( incase the url looks something like youtu.be )
       
  3556         if ( mediaType === "youtu" ) {
       
  3557           mediaType = "youtube";
       
  3558         }
       
  3559       }
       
  3560       else {
       
  3561         // if the regex didn't return anything we know it's an HTML5 source
       
  3562         mediaType = "HTML5";
       
  3563       }
       
  3564 
       
  3565       // Store Reference to Type for use in end
       
  3566       options._type = mediaType;
       
  3567 
       
  3568       // Create separate container for plugin
       
  3569       options._container = document.createElement( "div" );
       
  3570       container = options._container;
       
  3571       container.id = "mediaSpawnerdiv-" + Popcorn.guid();
       
  3572 
       
  3573       // Default width and height of media
       
  3574       options.width = options.width || 400;
       
  3575       options.height = options.height || 200;
       
  3576 
       
  3577       // Captions now need to be in their own container, due to the problem with flash players
       
  3578       // described in start/end
       
  3579       if ( options.caption ) {
       
  3580         capContainer = document.createElement( "div" );
       
  3581         capContainer.innerHTML = options.caption;
       
  3582         capContainer.style.display = "none";
       
  3583         options._capCont = capContainer;
       
  3584         container.appendChild( capContainer );
       
  3585       }
       
  3586 
       
  3587       target && target.appendChild( container );
       
  3588 
       
  3589       function constructMedia(){
       
  3590 
       
  3591         function checkPlayerTypeLoaded() {
       
  3592           if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] ) {
       
  3593             setTimeout( function() {
       
  3594               checkPlayerTypeLoaded();
       
  3595             }, 300 );
       
  3596           } else {
       
  3597             options.id = options._container.id;
       
  3598             // Set the width/height of the container before calling Popcorn.smart
       
  3599             // Allows youtube to pickup on the specified height an create the player
       
  3600             // with specified dimensions
       
  3601             options._container.style.width = options.width + "px";
       
  3602             options._container.style.height = options.height + "px";
       
  3603             options.popcorn = Popcorn.smart( "#" + options.id, options.source );
       
  3604 
       
  3605             if ( mediaType === "HTML5" ) {
       
  3606               options.popcorn.controls( true );
       
  3607             }
       
  3608             
       
  3609             // Set them to 0 now so it is hidden
       
  3610             options._container.style.width = "0px";
       
  3611             options._container.style.height = "0px";
       
  3612             options._container.style.visibility = "hidden";
       
  3613             options._container.style.overflow = "hidden";
       
  3614           }
       
  3615         }
       
  3616 
       
  3617         if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] && !playerTypeLoading[ mediaType ] ) {
       
  3618           playerTypeLoading[ mediaType ] = true;
       
  3619           Popcorn.getScript( "http://popcornjs.org/code/players/" + mediaType + "/popcorn." + mediaType + ".js", function() {
       
  3620             checkPlayerTypeLoaded();
       
  3621           });
       
  3622         }
       
  3623         else {
       
  3624           checkPlayerTypeLoaded();
       
  3625         }
       
  3626 
       
  3627       }
       
  3628 
       
  3629       // If Player script needed to be loaded, keep checking until it is and then fire readycallback
       
  3630       function isPlayerReady() {
       
  3631         if ( !window.Popcorn.player ) {
       
  3632           setTimeout( function () {
       
  3633             isPlayerReady();
       
  3634           }, 300 );
       
  3635         } else {
       
  3636           constructMedia();
       
  3637         }
       
  3638       }
       
  3639 
       
  3640       // If player script isn't present, retrieve script
       
  3641       if ( !window.Popcorn.player && !playerTypeLoading.module ) {
       
  3642         playerTypeLoading.module = true;
       
  3643         Popcorn.getScript( PLAYER_URL, isPlayerReady );
       
  3644       } else {
       
  3645         isPlayerReady();
       
  3646       }
       
  3647 
       
  3648     },
       
  3649     start: function( event, options ) {
       
  3650       if( options._capCont ) {
       
  3651         options._capCont.style.display = "";
       
  3652       }
       
  3653 
       
  3654       /* Using this style for Start/End is required because of the flash players
       
  3655        * Without it on end an internal cleanup is called, causing the flash players
       
  3656        * to be out of sync with Popcorn, as they are then rebuilt.
       
  3657        */
       
  3658       options._container.style.width = options.width + "px";
       
  3659       options._container.style.height = options.height + "px";
       
  3660       options._container.style.visibility = "visible";
       
  3661       options._container.style.overflow = "visible";
       
  3662 
       
  3663       if ( options.autoplay ) {
       
  3664         options.popcorn.play();
       
  3665       }
       
  3666     },
       
  3667     end: function( event, options ) {
       
  3668       if( options._capCont ) {
       
  3669         options._capCont.style.display = "none";
       
  3670       }
       
  3671 
       
  3672       /* Using this style for Start/End is required because of the flash players
       
  3673        * Without it on end an internal cleanup is called, causing the flash players
       
  3674        * to be out of sync with Popcorn, as they are then rebuilt.
       
  3675        */
       
  3676       options._container.style.width = "0px";
       
  3677       options._container.style.height = "0px";
       
  3678       options._container.style.visibility = "hidden";
       
  3679       options._container.style.overflow = "hidden";
       
  3680 
       
  3681       // Pause all popcorn instances on exit
       
  3682       options.popcorn.pause();
       
  3683 
       
  3684     },
       
  3685     _teardown: function( options ) {
       
  3686       if ( options.popcorn && options.popcorn.destory ) {
       
  3687         options.popcorn.destroy();
       
  3688       }
       
  3689       document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container );
       
  3690     }
       
  3691   });
       
  3692 })( Popcorn, this );
       
  3693 // PLUGIN: Code
       
  3694 
       
  3695 (function ( Popcorn ) {
       
  3696 
       
  3697   /**
       
  3698    * Code Popcorn Plug-in
       
  3699    *
       
  3700    * Adds the ability to run arbitrary code (JavaScript functions) according to video timing.
       
  3701    *
       
  3702    * @param {Object} options
       
  3703    *
       
  3704    * Required parameters: start, end, template, data, and target.
       
  3705    * Optional parameter: static.
       
  3706    *
       
  3707    *   start: the time in seconds when the mustache template should be rendered
       
  3708    *          in the target div.
       
  3709    *
       
  3710    *   end: the time in seconds when the rendered mustache template should be
       
  3711    *        removed from the target div.
       
  3712    *
       
  3713    *   onStart: the function to be run when the start time is reached.
       
  3714    *
       
  3715    *   onFrame: [optional] a function to be run on each paint call
       
  3716    *            (e.g., called ~60 times per second) between the start and end times.
       
  3717    *
       
  3718    *   onEnd: [optional] a function to be run when the end time is reached.
       
  3719    *
       
  3720    * Example:
       
  3721      var p = Popcorn('#video')
       
  3722 
       
  3723         // onStart function only
       
  3724         .code({
       
  3725           start: 1,
       
  3726           end: 4,
       
  3727           onStart: function( options ) {
       
  3728             // called on start
       
  3729           }
       
  3730         })
       
  3731 
       
  3732         // onStart + onEnd only
       
  3733         .code({
       
  3734           start: 6,
       
  3735           end: 8,
       
  3736           onStart: function( options ) {
       
  3737             // called on start
       
  3738           },
       
  3739           onEnd: function ( options ) {
       
  3740             // called on end
       
  3741           }
       
  3742         })
       
  3743 
       
  3744         // onStart, onEnd, onFrame
       
  3745         .code({
       
  3746           start: 10,
       
  3747           end: 14,
       
  3748           onStart: function( options ) {
       
  3749             // called on start
       
  3750           },
       
  3751           onFrame: function ( options ) {
       
  3752             // called on every paint frame between start and end.
       
  3753             // uses mozRequestAnimationFrame, webkitRequestAnimationFrame,
       
  3754             // or setTimeout with 16ms window.
       
  3755           },
       
  3756           onEnd: function ( options ) {
       
  3757             // called on end
       
  3758           }
       
  3759         });
       
  3760   *
       
  3761   */
       
  3762 
       
  3763   Popcorn.plugin( "code" , function( options ) {
       
  3764     var running = false,
       
  3765         instance = this;
       
  3766 
       
  3767     // Setup a proper frame interval function (60fps), favouring paint events.
       
  3768     var step = (function() {
       
  3769 
       
  3770       var buildFrameRunner = function( runner ) {
       
  3771         return function( f, options ) {
       
  3772 
       
  3773           var _f = function() {
       
  3774             running && f.call( instance, options );
       
  3775             running && runner( _f );
       
  3776           };
       
  3777 
       
  3778           _f();
       
  3779         };
       
  3780       };
       
  3781 
       
  3782       // Figure out which level of browser support we have for this
       
  3783       if ( window.webkitRequestAnimationFrame ) {
       
  3784         return buildFrameRunner( window.webkitRequestAnimationFrame );
       
  3785       } else if ( window.mozRequestAnimationFrame ) {
       
  3786         return buildFrameRunner( window.mozRequestAnimationFrame );
       
  3787       } else {
       
  3788         return buildFrameRunner( function( f ) {
       
  3789           window.setTimeout( f, 16 );
       
  3790         });
       
  3791       }
       
  3792 
       
  3793     })();
       
  3794 
       
  3795     if ( !options.onStart || typeof options.onStart !== "function" ) {
       
  3796 
       
  3797       options.onStart = Popcorn.nop;
       
  3798     }
       
  3799 
       
  3800     if ( options.onEnd && typeof options.onEnd !== "function" ) {
       
  3801 
       
  3802       options.onEnd = undefined;
       
  3803     }
       
  3804 
       
  3805     if ( options.onFrame && typeof options.onFrame !== "function" ) {
       
  3806 
       
  3807       options.onFrame = undefined;
       
  3808     }
       
  3809 
       
  3810     return {
       
  3811       start: function( event, options ) {
       
  3812         options.onStart.call( instance, options );
       
  3813 
       
  3814         if ( options.onFrame ) {
       
  3815           running = true;
       
  3816           step( options.onFrame, options );
       
  3817         }
       
  3818       },
       
  3819 
       
  3820       end: function( event, options ) {
       
  3821         if ( options.onFrame ) {
       
  3822           running = false;
       
  3823         }
       
  3824 
       
  3825         if ( options.onEnd ) {
       
  3826           options.onEnd.call( instance, options );
       
  3827         }
       
  3828       }
       
  3829     };
       
  3830   },
       
  3831   {
       
  3832     about: {
       
  3833       name: "Popcorn Code Plugin",
       
  3834       version: "0.1",
       
  3835       author: "David Humphrey (@humphd)",
       
  3836       website: "http://vocamus.net/dave"
       
  3837     },
       
  3838     options: {
       
  3839       start: {
       
  3840        elem: "input",
       
  3841        type: "number",
       
  3842        label: "Start"
       
  3843       },
       
  3844       end: {
       
  3845         elem: "input",
       
  3846         type: "number",
       
  3847         label: "End"
       
  3848       },
       
  3849       onStart: {
       
  3850         elem: "input",
       
  3851         type: "function",
       
  3852         label: "onStart"
       
  3853       },
       
  3854       onFrame: {
       
  3855         elem: "input",
       
  3856         type: "function",
       
  3857         label: "onFrame",
       
  3858         optional: true
       
  3859       },
       
  3860       onEnd: {
       
  3861         elem: "input",
       
  3862         type: "function",
       
  3863         label: "onEnd"
       
  3864       }
       
  3865     }
       
  3866   });
       
  3867 })( Popcorn );
       
  3868 // PLUGIN: Flickr
       
  3869 (function (Popcorn) {
       
  3870 
       
  3871   /**
       
  3872    * Flickr popcorn plug-in
       
  3873    * Appends a users Flickr images to an element on the page.
       
  3874    * Options parameter will need a start, end, target and userid or username and api_key.
       
  3875    * Optional parameters are numberofimages, height, width, padding, and border
       
  3876    * Start is the time that you want this plug-in to execute (in seconds)
       
  3877    * End is the time that you want this plug-in to stop executing (in seconds)
       
  3878    * Userid is the id of who's Flickr images you wish to show
       
  3879    * Tags is a mutually exclusive list of image descriptor tags
       
  3880    * Username is the username of who's Flickr images you wish to show
       
  3881    *  using both userid and username is redundant
       
  3882    *  an api_key is required when using username
       
  3883    * Apikey is your own api key provided by Flickr
       
  3884    * Target is the id of the document element that the images are
       
  3885    *  appended to, this target element must exist on the DOM
       
  3886    * Numberofimages specify the number of images to retreive from flickr, defaults to 4
       
  3887    * Height the height of the image, defaults to '50px'
       
  3888    * Width the width of the image, defaults to '50px'
       
  3889    * Padding number of pixels between images, defaults to '5px'
       
  3890    * Border border size in pixels around images, defaults to '0px'
       
  3891    *
       
  3892    * @param {Object} options
       
  3893    *
       
  3894    * Example:
       
  3895      var p = Popcorn('#video')
       
  3896         .flickr({
       
  3897           start:          5,                 // seconds, mandatory
       
  3898           end:            15,                // seconds, mandatory
       
  3899           userid:         '35034346917@N01', // optional
       
  3900           tags:           'dogs,cats',       // optional
       
  3901           numberofimages: '8',               // optional
       
  3902           height:         '50px',            // optional
       
  3903           width:          '50px',            // optional
       
  3904           padding:        '5px',             // optional
       
  3905           border:         '0px',             // optional
       
  3906           target:         'flickrdiv'        // mandatory
       
  3907         } )
       
  3908    *
       
  3909    */
       
  3910 
       
  3911   var idx = 0;
       
  3912 
       
  3913   Popcorn.plugin( "flickr" , function( options ) {
       
  3914     var containerDiv,
       
  3915         target = document.getElementById( options.target ),
       
  3916         _userid,
       
  3917         _uri,
       
  3918         _link,
       
  3919         _image,
       
  3920         _count = options.numberofimages || 4,
       
  3921         _height = options.height || "50px",
       
  3922         _width = options.width || "50px",
       
  3923         _padding = options.padding || "5px",
       
  3924         _border = options.border || "0px";
       
  3925 
       
  3926     // create a new div this way anything in the target div is left intact
       
  3927     // this is later populated with Flickr images
       
  3928     containerDiv = document.createElement( "div" );
       
  3929     containerDiv.id = "flickr" + idx;
       
  3930     containerDiv.style.width = "100%";
       
  3931     containerDiv.style.height = "100%";
       
  3932     containerDiv.style.display = "none";
       
  3933     idx++;
       
  3934 
       
  3935     target && target.appendChild( containerDiv );
       
  3936 
       
  3937     // get the userid from Flickr API by using the username and apikey
       
  3938     var isUserIDReady = function() {
       
  3939       if ( !_userid ) {
       
  3940 
       
  3941         _uri  = "http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&";
       
  3942         _uri += "username=" + options.username + "&api_key=" + options.apikey + "&format=json&jsoncallback=flickr";
       
  3943         Popcorn.getJSONP( _uri, function( data ) {
       
  3944           _userid = data.user.nsid;
       
  3945           getFlickrData();
       
  3946         });
       
  3947 
       
  3948       } else {
       
  3949 
       
  3950         setTimeout(function () {
       
  3951           isUserIDReady();
       
  3952         }, 5 );
       
  3953       }
       
  3954     };
       
  3955 
       
  3956     // get the photos from Flickr API by using the user_id and/or tags
       
  3957     var getFlickrData = function() {
       
  3958 
       
  3959       _uri  = "http://api.flickr.com/services/feeds/photos_public.gne?";
       
  3960 
       
  3961       if ( _userid ) {
       
  3962         _uri += "id=" + _userid + "&";
       
  3963       }
       
  3964       if ( options.tags ) {
       
  3965         _uri += "tags=" + options.tags + "&";
       
  3966       }
       
  3967 
       
  3968       _uri += "lang=en-us&format=json&jsoncallback=flickr";
       
  3969 
       
  3970       Popcorn.xhr.getJSONP( _uri, function( data ) {
       
  3971 
       
  3972         var fragment = document.createElement( "div" );
       
  3973 
       
  3974         fragment.innerHTML = "<p style='padding:" + _padding + ";'>" + data.title + "<p/>";
       
  3975 
       
  3976         Popcorn.forEach( data.items, function ( item, i ) {
       
  3977           if ( i < _count ) {
       
  3978 
       
  3979             _link = document.createElement( "a" );
       
  3980             _link.setAttribute( "href", item.link );
       
  3981             _link.setAttribute( "target", "_blank" );
       
  3982             _image = document.createElement( "img" );
       
  3983             _image.setAttribute( "src", item.media.m );
       
  3984             _image.setAttribute( "height",_height );
       
  3985             _image.setAttribute( "width", _width );
       
  3986             _image.setAttribute( "style", "border:" + _border + ";padding:" + _padding );
       
  3987             _link.appendChild( _image );
       
  3988             fragment.appendChild( _link );
       
  3989 
       
  3990           } else {
       
  3991 
       
  3992             return false;
       
  3993           }
       
  3994         });
       
  3995 
       
  3996         containerDiv.appendChild( fragment );
       
  3997       });
       
  3998     };
       
  3999 
       
  4000     if ( options.username && options.apikey ) {
       
  4001       isUserIDReady();
       
  4002     }
       
  4003     else {
       
  4004       _userid = options.userid;
       
  4005       getFlickrData();
       
  4006     }
       
  4007     return {
       
  4008       /**
       
  4009        * @member flickr
       
  4010        * The start function will be executed when the currentTime
       
  4011        * of the video reaches the start time provided by the
       
  4012        * options variable
       
  4013        */
       
  4014       start: function( event, options ) {
       
  4015         containerDiv.style.display = "inline";
       
  4016       },
       
  4017       /**
       
  4018        * @member flickr
       
  4019        * The end function will be executed when the currentTime
       
  4020        * of the video reaches the end time provided by the
       
  4021        * options variable
       
  4022        */
       
  4023       end: function( event, options ) {
       
  4024         containerDiv.style.display = "none";
       
  4025       },
       
  4026       _teardown: function( options ) {
       
  4027         document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( containerDiv );
       
  4028       }
       
  4029     };
       
  4030   },
       
  4031   {
       
  4032     about: {
       
  4033       name: "Popcorn Flickr Plugin",
       
  4034       version: "0.2",
       
  4035       author: "Scott Downe, Steven Weerdenburg, Annasob",
       
  4036       website: "http://scottdowne.wordpress.com/"
       
  4037     },
       
  4038     options: {
       
  4039       start: {
       
  4040         elem: "input",
       
  4041         type: "number",
       
  4042         label: "Start"
       
  4043       },
       
  4044       end: {
       
  4045         elem: "input",
       
  4046         type: "number",
       
  4047         label: "End"
       
  4048       },
       
  4049       userid: {
       
  4050         elem: "input",
       
  4051         type: "text",
       
  4052         label: "User ID",
       
  4053         optional: true
       
  4054       },
       
  4055       tags: {
       
  4056         elem: "input",
       
  4057         type: "text",
       
  4058         label: "Tags"
       
  4059       },
       
  4060       username: {
       
  4061         elem: "input",
       
  4062         type: "text",
       
  4063         label: "Username",
       
  4064         optional: true
       
  4065       },
       
  4066       apikey: {
       
  4067         elem: "input",
       
  4068         type: "text",
       
  4069         label: "API Key",
       
  4070         optional: true
       
  4071       },
       
  4072       target: "flickr-container",
       
  4073       height: {
       
  4074         elem: "input",
       
  4075         type: "text",
       
  4076         label: "Height",
       
  4077         "default": "50px",
       
  4078         optional: true
       
  4079       },
       
  4080       width: {
       
  4081         elem: "input",
       
  4082         type: "text",
       
  4083         label: "Width",
       
  4084         "default": "50px",
       
  4085         optional: true
       
  4086       },
       
  4087       padding: {
       
  4088         elem: "input",
       
  4089         type: "text",
       
  4090         label: "Padding",
       
  4091         optional: true
       
  4092       },
       
  4093       border: {
       
  4094         elem: "input",
       
  4095         type: "text",
       
  4096         label: "Border",
       
  4097         "default": "5px",
       
  4098         optional: true
       
  4099       },
       
  4100       numberofimages: {
       
  4101         elem: "input",
       
  4102         type: "number",
       
  4103         "default": 4,
       
  4104         label: "Number of Images"
       
  4105       }
       
  4106     }
       
  4107   });
       
  4108 })( Popcorn );
       
  4109 // PLUGIN: Footnote/Text
       
  4110 
       
  4111 (function ( Popcorn ) {
       
  4112 
       
  4113   /**
       
  4114    * Footnote popcorn plug-in
       
  4115    * Adds text to an element on the page.
       
  4116    * Options parameter will need a start, end, target and text.
       
  4117    * Start is the time that you want this plug-in to execute
       
  4118    * End is the time that you want this plug-in to stop executing
       
  4119    * Text is the text that you want to appear in the target
       
  4120    * Target is the id of the document element that the text needs to be
       
  4121    * attached to, this target element must exist on the DOM
       
  4122    *
       
  4123    * @param {Object} options
       
  4124    *
       
  4125    * Example:
       
  4126    *  var p = Popcorn('#video')
       
  4127    *    .footnote({
       
  4128    *      start: 5, // seconds
       
  4129    *      end: 15, // seconds
       
  4130    *      text: 'This video made exclusively for drumbeat.org',
       
  4131    *      target: 'footnotediv'
       
  4132    *    });
       
  4133    **/
       
  4134 
       
  4135   Popcorn.plugin( "footnote", {
       
  4136 
       
  4137     manifest: {
       
  4138       about: {
       
  4139         name: "Popcorn Footnote Plugin",
       
  4140         version: "0.2",
       
  4141         author: "@annasob, @rwaldron",
       
  4142         website: "annasob.wordpress.com"
       
  4143       },
       
  4144       options: {
       
  4145         start: {
       
  4146           elem: "input",
       
  4147           type: "number",
       
  4148           label: "Start"
       
  4149         },
       
  4150         end: {
       
  4151           elem: "input",
       
  4152           type: "number",
       
  4153           label: "End"
       
  4154         },
       
  4155         text: {
       
  4156           elem: "input",
       
  4157           type: "text",
       
  4158           label: "Text"
       
  4159         },
       
  4160         target: "footnote-container"
       
  4161       }
       
  4162     },
       
  4163 
       
  4164     _setup: function( options ) {
       
  4165 
       
  4166       var target = Popcorn.dom.find( options.target );
       
  4167 
       
  4168       options._container = document.createElement( "div" );
       
  4169       options._container.style.display = "none";
       
  4170       options._container.innerHTML  = options.text;
       
  4171 
       
  4172       target.appendChild( options._container );
       
  4173     },
       
  4174 
       
  4175     /**
       
  4176      * @member footnote
       
  4177      * The start function will be executed when the currentTime
       
  4178      * of the video  reaches the start time provided by the
       
  4179      * options variable
       
  4180      */
       
  4181     start: function( event, options ){
       
  4182       options._container.style.display = "inline";
       
  4183     },
       
  4184 
       
  4185     /**
       
  4186      * @member footnote
       
  4187      * The end function will be executed when the currentTime
       
  4188      * of the video  reaches the end time provided by the
       
  4189      * options variable
       
  4190      */
       
  4191     end: function( event, options ){
       
  4192       options._container.style.display = "none";
       
  4193     },
       
  4194 
       
  4195     _teardown: function( options ) {
       
  4196       var target = Popcorn.dom.find( options.target );
       
  4197       if ( target ) {
       
  4198         target.removeChild( options._container );
       
  4199       }
       
  4200     }
       
  4201 
       
  4202   });
       
  4203 })( Popcorn );
       
  4204 // PLUGIN: Text
       
  4205 
       
  4206 (function ( Popcorn ) {
       
  4207 
       
  4208   /**
       
  4209    * Text Popcorn plug-in
       
  4210    *
       
  4211    * Places text in an element on the page.  Plugin options include:
       
  4212    * Options parameter will need a start, end.
       
  4213    *   Start: Is the time that you want this plug-in to execute
       
  4214    *   End: Is the time that you want this plug-in to stop executing
       
  4215    *   Text: Is the text that you want to appear in the target
       
  4216    *   Escape: {true|false} Whether to escape the text (e.g., html strings)
       
  4217    *   Multiline: {true|false} Whether newlines should be turned into <br>s
       
  4218    *   Target: Is the ID of the element where the text should be placed. An empty target
       
  4219    *           will be placed on top of the media element
       
  4220    *
       
  4221    * @param {Object} options
       
  4222    *
       
  4223    * Example:
       
  4224    *  var p = Popcorn('#video')
       
  4225    *
       
  4226    *    // Simple text
       
  4227    *    .text({
       
  4228    *      start: 5, // seconds
       
  4229    *      end: 15, // seconds
       
  4230    *      text: 'This video made exclusively for drumbeat.org',
       
  4231    *      target: 'textdiv'
       
  4232    *     })
       
  4233    *
       
  4234    *    // HTML text, rendered as HTML
       
  4235    *    .text({
       
  4236    *      start: 15, // seconds
       
  4237    *      end: 20, // seconds
       
  4238    *      text: '<p>This video made <em>exclusively</em> for drumbeat.org</p>',
       
  4239    *      target: 'textdiv'
       
  4240    *    })
       
  4241    *
       
  4242    *    // HTML text, escaped and rendered as plain text
       
  4243    *    .text({
       
  4244    *      start: 20, // seconds
       
  4245    *      end: 25, // seconds
       
  4246    *      text: 'This is an HTML p element: <p>paragraph</p>',
       
  4247    *      escape: true,
       
  4248    *      target: 'textdiv'
       
  4249    *    })
       
  4250    *
       
  4251    *    // Multi-Line HTML text, escaped and rendered as plain text
       
  4252    *    .text({
       
  4253    *      start: 25, // seconds
       
  4254    *      end: 30, // seconds
       
  4255    *      text: 'This is an HTML p element: <p>paragraph</p>\nThis is an HTML b element: <b>bold</b>',
       
  4256    *      escape: true,
       
  4257    *      multiline: true,
       
  4258    *      target: 'textdiv'
       
  4259    *    });
       
  4260    *
       
  4261    *    // Subtitle text
       
  4262    *    .text({
       
  4263    *      start: 30, // seconds
       
  4264    *      end: 40, // seconds
       
  4265    *      text: 'This will be overlayed on the video',
       
  4266    *     })
       
  4267    **/
       
  4268 
       
  4269   /**
       
  4270    * HTML escape code from mustache.js, used under MIT Licence
       
  4271    * https://github.com/janl/mustache.js/blob/master/mustache.js
       
  4272    **/
       
  4273   var escapeMap = {
       
  4274     "&": "&amp;",
       
  4275     "<": "&lt;",
       
  4276     ">": "&gt;",
       
  4277     '"': '&quot;',
       
  4278     "'": '&#39;'
       
  4279   };
       
  4280 
       
  4281   function escapeHTML( string, multiline ) {
       
  4282     return String( string ).replace( /&(?!\w+;)|[<>"']/g, function ( s ) {
       
  4283       return escapeMap[ s ] || s;
       
  4284     });
       
  4285   }
       
  4286 
       
  4287   function newlineToBreak( string ) {
       
  4288     // Deal with both \r\n and \n
       
  4289     return string.replace( /\r?\n/gm, "<br>" );
       
  4290   }
       
  4291 
       
  4292   // Subtitle specific functionality
       
  4293   function createSubtitleContainer( context, id ) {
       
  4294 
       
  4295     var ctxContainer = context.container = document.createElement( "div" ),
       
  4296         style = ctxContainer.style,
       
  4297         media = context.media;
       
  4298 
       
  4299     var updatePosition = function() {
       
  4300       var position = context.position();
       
  4301       // the video element must have height and width defined
       
  4302       style.fontSize = "18px";
       
  4303       style.width = media.offsetWidth + "px";
       
  4304       style.top = position.top  + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px";
       
  4305       style.left = position.left + "px";
       
  4306 
       
  4307       setTimeout( updatePosition, 10 );
       
  4308     };
       
  4309 
       
  4310     ctxContainer.id = id || "";
       
  4311     style.position = "absolute";
       
  4312     style.color = "white";
       
  4313     style.textShadow = "black 2px 2px 6px";
       
  4314     style.fontWeight = "bold";
       
  4315     style.textAlign = "center";
       
  4316 
       
  4317     updatePosition();
       
  4318 
       
  4319     context.media.parentNode.appendChild( ctxContainer );
       
  4320 
       
  4321     return ctxContainer;
       
  4322   }
       
  4323 
       
  4324   Popcorn.plugin( "text", {
       
  4325 
       
  4326     manifest: {
       
  4327       about: {
       
  4328         name: "Popcorn Text Plugin",
       
  4329         version: "0.1",
       
  4330         author: "@humphd"
       
  4331       },
       
  4332       options: {
       
  4333         start: {
       
  4334           elem: "input",
       
  4335           type: "number",
       
  4336           label: "Start"
       
  4337         },
       
  4338         end: {
       
  4339           elem: "input",
       
  4340           type: "number",
       
  4341           label: "End"
       
  4342         },
       
  4343         text: {
       
  4344           elem: "input",
       
  4345           type: "text",
       
  4346           label: "Text",
       
  4347           "default": "Popcorn.js"
       
  4348         },
       
  4349         escape: {
       
  4350           elem: "input",
       
  4351           type: "checkbox",
       
  4352           label: "Escape"
       
  4353         },
       
  4354         multiline: {
       
  4355           elem: "input",
       
  4356           type: "checkbox",
       
  4357           label: "Multiline"
       
  4358         }
       
  4359       }
       
  4360     },
       
  4361 
       
  4362     _setup: function( options ) {
       
  4363 
       
  4364       var target,
       
  4365           text,
       
  4366           container = options._container = document.createElement( "div" );
       
  4367 
       
  4368       container.style.display = "none";
       
  4369 
       
  4370       if ( options.target ) {
       
  4371         // Try to use supplied target
       
  4372         target = Popcorn.dom.find( options.target );
       
  4373 
       
  4374         if ( !target ) {
       
  4375           target = createSubtitleContainer( this, options.target );
       
  4376         }
       
  4377         else if ( [ "VIDEO", "AUDIO" ].indexOf( target.nodeName ) > -1 ) {
       
  4378           target = createSubtitleContainer( this, options.target + "-overlay" );
       
  4379         }
       
  4380 
       
  4381       } else if ( !this.container ) {
       
  4382         // Creates a div for all subtitles to use
       
  4383         target = createSubtitleContainer( this );
       
  4384 
       
  4385       } else {
       
  4386         // Use subtitle container
       
  4387         target = this.container;
       
  4388       }
       
  4389 
       
  4390       // cache reference to actual target container
       
  4391       options._target = target;
       
  4392 
       
  4393       // Escape HTML text if requested
       
  4394       text = !!options.escape ? escapeHTML( options.text ) :
       
  4395                                     options.text;
       
  4396 
       
  4397       // Swap newline for <br> if requested
       
  4398       text = !!options.multiline ? newlineToBreak ( text ) : text;
       
  4399       container.innerHTML = text || "";
       
  4400 
       
  4401       target.appendChild( container );
       
  4402     },
       
  4403 
       
  4404     /**
       
  4405      * @member text
       
  4406      * The start function will be executed when the currentTime
       
  4407      * of the video  reaches the start time provided by the
       
  4408      * options variable
       
  4409      */
       
  4410     start: function( event, options ) {
       
  4411       options._container.style.display = "inline";
       
  4412     },
       
  4413 
       
  4414     /**
       
  4415      * @member text
       
  4416      * The end function will be executed when the currentTime
       
  4417      * of the video  reaches the end time provided by the
       
  4418      * options variable
       
  4419      */
       
  4420     end: function( event, options ) {
       
  4421       options._container.style.display = "none";
       
  4422     },
       
  4423 
       
  4424     _teardown: function( options ) {
       
  4425       var target = options._target;
       
  4426       if ( target ) {
       
  4427         target.removeChild( options._container );
       
  4428       }
       
  4429     }
       
  4430   });
       
  4431 })( Popcorn );
       
  4432 
       
  4433 // PLUGIN: Google Maps
       
  4434 var googleCallback;
       
  4435 (function ( Popcorn ) {
       
  4436 
       
  4437   var i = 1,
       
  4438     _mapFired = false,
       
  4439     _mapLoaded = false,
       
  4440     geocoder, loadMaps;
       
  4441   //google api callback
       
  4442   googleCallback = function ( data ) {
       
  4443     // ensure all of the maps functions needed are loaded
       
  4444     // before setting _maploaded to true
       
  4445     if ( typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng ) {
       
  4446       geocoder = new google.maps.Geocoder();
       
  4447       Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js", function() {
       
  4448         _mapLoaded = true;
       
  4449       });
       
  4450     } else {
       
  4451       setTimeout(function () {
       
  4452         googleCallback( data );
       
  4453       }, 1);
       
  4454     }
       
  4455   };
       
  4456   // function that loads the google api
       
  4457   loadMaps = function () {
       
  4458     // for some reason the Google Map API adds content to the body
       
  4459     if ( document.body ) {
       
  4460       _mapFired = true;
       
  4461       Popcorn.getScript( "//maps.google.com/maps/api/js?sensor=false&callback=googleCallback" );
       
  4462     } else {
       
  4463       setTimeout(function () {
       
  4464         loadMaps();
       
  4465       }, 1);
       
  4466     }
       
  4467   };
       
  4468 
       
  4469   function buildMap( options, location, mapDiv ) {
       
  4470     var type = options.type ? options.type.toUpperCase() : "HYBRID",
       
  4471       layer;
       
  4472 
       
  4473     // See if we need to make a custom Stamen map layer
       
  4474     if ( type === "STAMEN-WATERCOLOR" ||
       
  4475          type === "STAMEN-TERRAIN"    ||
       
  4476          type === "STAMEN-TONER" ) {
       
  4477       // Stamen types are lowercase
       
  4478       layer = type.replace("STAMEN-", "").toLowerCase();
       
  4479     }
       
  4480 
       
  4481     var map = new google.maps.Map( mapDiv, {
       
  4482       // If a custom layer was specified, use that, otherwise use type
       
  4483       mapTypeId: layer ? layer : google.maps.MapTypeId[ type ],
       
  4484       // Hide the layer selection UI
       
  4485       mapTypeControlOptions: { mapTypeIds: [] }
       
  4486     });
       
  4487 
       
  4488     if ( layer ) {
       
  4489       map.mapTypes.set( layer, new google.maps.StamenMapType( layer ));
       
  4490     }
       
  4491     map.getDiv().style.display = "none";
       
  4492 
       
  4493     return map;
       
  4494   }
       
  4495 
       
  4496   /**
       
  4497    * googlemap popcorn plug-in
       
  4498    * Adds a map to the target div centered on the location specified by the user
       
  4499    * Options parameter will need a start, end, target, type, zoom, lat and lng, and location
       
  4500    * -Start is the time that you want this plug-in to execute
       
  4501    * -End is the time that you want this plug-in to stop executing
       
  4502    * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
       
  4503    * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW, or one of the
       
  4504    *                          Stamen custom map types (http://http://maps.stamen.com): STAMEN-TONER,
       
  4505    *                          STAMEN-WATERCOLOR, or STAMEN-TERRAIN.
       
  4506    * -Zoom [optional] defaults to 0
       
  4507    * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect)
       
  4508    * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended)
       
  4509    * -Lat and Lng: the coordinates of the map must be present if location is not specified.
       
  4510    * -Height [optional] the height of the map, in "px" or "%". Defaults to "100%".
       
  4511    * -Width [optional] the width of the map, in "px" or "%". Defaults to "100%".
       
  4512    * -Location: the adress you want the map to display, must be present if lat and lng are not specified.
       
  4513    * Note: using location requires extra loading time, also not specifying both lat/lng and location will
       
  4514    * cause and error.
       
  4515    *
       
  4516    * Tweening works using the following specifications:
       
  4517    * -location is the start point when using an auto generated route
       
  4518    * -tween when used in this context is a string which specifies the end location for your route
       
  4519    * Note that both location and tween must be present when using an auto generated route, or the map will not tween
       
  4520    * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
       
  4521    * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route
       
  4522    *
       
  4523    * -tween is an array of objects, each containing data for one frame of a tween
       
  4524    * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work
       
  4525    * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0
       
  4526    * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
       
  4527    *
       
  4528    * @param {Object} options
       
  4529    *
       
  4530    * Example:
       
  4531    var p = Popcorn("#video")
       
  4532    .googlemap({
       
  4533    start: 5, // seconds
       
  4534    end: 15, // seconds
       
  4535    type: "ROADMAP",
       
  4536    target: "map",
       
  4537    lat: 43.665429,
       
  4538    lng: -79.403323
       
  4539    } )
       
  4540    *
       
  4541    */
       
  4542   Popcorn.plugin( "googlemap", function ( options ) {
       
  4543     var newdiv, map, location,
       
  4544         target = document.getElementById( options.target );
       
  4545 
       
  4546     options.type = options.type || "ROADMAP";
       
  4547     options.zoom = options.zoom || 1;
       
  4548     options.lat = options.lat || 0;
       
  4549     options.lng = options.lng || 0;
       
  4550 
       
  4551     // if this is the firest time running the plugins
       
  4552     // call the function that gets the sctipt
       
  4553     if ( !_mapFired ) {
       
  4554       loadMaps();
       
  4555     }
       
  4556 
       
  4557     // create a new div this way anything in the target div is left intact
       
  4558     // this is later passed on to the maps api
       
  4559     newdiv = document.createElement( "div" );
       
  4560     newdiv.id = "actualmap" + i;
       
  4561     newdiv.style.width = options.width || "100%";
       
  4562 
       
  4563     // height is a little more complicated than width.
       
  4564     if ( options.height ) {
       
  4565       newdiv.style.height = options.height;
       
  4566     } else if ( target && target.clientHeight ) {
       
  4567       newdiv.style.height = target.clientHeight + "px";
       
  4568     } else {
       
  4569       newdiv.style.height = "100%";
       
  4570     }
       
  4571 
       
  4572     i++;
       
  4573 
       
  4574     target && target.appendChild( newdiv );
       
  4575 
       
  4576     // ensure that google maps and its functions are loaded
       
  4577     // before setting up the map parameters
       
  4578     var isMapReady = function () {
       
  4579       if ( _mapLoaded ) {
       
  4580         if ( newdiv ) {
       
  4581           if ( options.location ) {
       
  4582             // calls an anonymous google function called on separate thread
       
  4583             geocoder.geocode({
       
  4584               "address": options.location
       
  4585             }, function ( results, status ) {
       
  4586               // second check for newdiv since it could have disappeared before
       
  4587               // this callback is actual run
       
  4588               if ( newdiv && status === google.maps.GeocoderStatus.OK ) {
       
  4589                 options.lat = results[ 0 ].geometry.location.lat();
       
  4590                 options.lng = results[ 0 ].geometry.location.lng();
       
  4591                 location = new google.maps.LatLng( options.lat, options.lng );
       
  4592                 map = buildMap( options, location, newdiv );
       
  4593               }
       
  4594             });
       
  4595           } else {
       
  4596             location = new google.maps.LatLng( options.lat, options.lng );
       
  4597             map = buildMap( options, location, newdiv );
       
  4598           }
       
  4599         }
       
  4600       } else {
       
  4601           setTimeout(function () {
       
  4602             isMapReady();
       
  4603           }, 5);
       
  4604         }
       
  4605       };
       
  4606 
       
  4607     isMapReady();
       
  4608 
       
  4609     return {
       
  4610       /**
       
  4611        * @member webpage
       
  4612        * The start function will be executed when the currentTime
       
  4613        * of the video reaches the start time provided by the
       
  4614        * options variable
       
  4615        */
       
  4616       start: function( event, options ) {
       
  4617         var that = this,
       
  4618             sView;
       
  4619 
       
  4620         // ensure the map has been initialized in the setup function above
       
  4621         var isMapSetup = function() {
       
  4622           if ( map ) {
       
  4623             options._map = map;
       
  4624 
       
  4625             map.getDiv().style.display = "block";
       
  4626             // reset the location and zoom just in case the user plaid with the map
       
  4627             google.maps.event.trigger( map, "resize" );
       
  4628             map.setCenter( location );
       
  4629 
       
  4630             // make sure options.zoom is a number
       
  4631             if ( options.zoom && typeof options.zoom !== "number" ) {
       
  4632               options.zoom = +options.zoom;
       
  4633             }
       
  4634 
       
  4635             map.setZoom( options.zoom );
       
  4636 
       
  4637             //Make sure heading is a number
       
  4638             if ( options.heading && typeof options.heading !== "number" ) {
       
  4639               options.heading = +options.heading;
       
  4640             }
       
  4641             //Make sure pitch is a number
       
  4642             if ( options.pitch && typeof options.pitch !== "number" ) {
       
  4643               options.pitch = +options.pitch;
       
  4644             }
       
  4645 
       
  4646             if ( options.type === "STREETVIEW" ) {
       
  4647               // Switch this map into streeview mode
       
  4648               map.setStreetView(
       
  4649               // Pass a new StreetViewPanorama instance into our map
       
  4650 
       
  4651                 sView = new google.maps.StreetViewPanorama( newdiv, {
       
  4652                   position: location,
       
  4653                   pov: {
       
  4654                     heading: options.heading = options.heading || 0,
       
  4655                     pitch: options.pitch = options.pitch || 0,
       
  4656                     zoom: options.zoom
       
  4657                   }
       
  4658                 })
       
  4659               );
       
  4660 
       
  4661               //  Function to handle tweening using a set timeout
       
  4662               var tween = function( rM, t ) {
       
  4663 
       
  4664                 var computeHeading = google.maps.geometry.spherical.computeHeading;
       
  4665                 setTimeout(function() {
       
  4666 
       
  4667                   var current_time = that.media.currentTime;
       
  4668 
       
  4669                   //  Checks whether this is a generated route or not
       
  4670                   if ( typeof options.tween === "object" ) {
       
  4671 
       
  4672                     for ( var i = 0, m = rM.length; i < m; i++ ) {
       
  4673 
       
  4674                       var waypoint = rM[ i ];
       
  4675 
       
  4676                       //  Checks if this position along the tween should be displayed or not
       
  4677                       if ( current_time >= ( waypoint.interval * ( i + 1 ) ) / 1000 &&
       
  4678                          ( current_time <= ( waypoint.interval * ( i + 2 ) ) / 1000 ||
       
  4679                            current_time >= waypoint.interval * ( m ) / 1000 ) ) {
       
  4680 
       
  4681                         sView3.setPosition( new google.maps.LatLng( waypoint.position.lat, waypoint.position.lng ) );
       
  4682 
       
  4683                         sView3.setPov({
       
  4684                           heading: waypoint.pov.heading || computeHeading( waypoint, rM[ i + 1 ] ) || 0,
       
  4685                           zoom: waypoint.pov.zoom || 0,
       
  4686                           pitch: waypoint.pov.pitch || 0
       
  4687                         });
       
  4688                       }
       
  4689                     }
       
  4690 
       
  4691                     //  Calls the tween function again at the interval set by the user
       
  4692                     tween( rM, rM[ 0 ].interval );
       
  4693                   } else {
       
  4694 
       
  4695                     for ( var k = 0, l = rM.length; k < l; k++ ) {
       
  4696 
       
  4697                       var interval = options.interval;
       
  4698 
       
  4699                       if( current_time >= (interval * ( k + 1 ) ) / 1000 &&
       
  4700                         ( current_time <= (interval * ( k + 2 ) ) / 1000 ||
       
  4701                           current_time >= interval * ( l ) / 1000 ) ) {
       
  4702 
       
  4703                         sView2.setPov({
       
  4704                           heading: computeHeading( rM[ k ], rM[ k + 1 ] ) || 0,
       
  4705                           zoom: options.zoom,
       
  4706                           pitch: options.pitch || 0
       
  4707                         });
       
  4708                         sView2.setPosition( checkpoints[ k ] );
       
  4709                       }
       
  4710                     }
       
  4711 
       
  4712                     tween( checkpoints, options.interval );
       
  4713                   }
       
  4714                 }, t );
       
  4715               };
       
  4716 
       
  4717               //  Determines if we should use hardcoded values ( using options.tween ),
       
  4718               //  or if we should use a start and end location and let google generate
       
  4719               //  the route for us
       
  4720               if ( options.location && typeof options.tween === "string" ) {
       
  4721 
       
  4722               //  Creating another variable to hold the streetview map for tweening,
       
  4723               //  Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps
       
  4724               var sView2 = sView;
       
  4725 
       
  4726                 //  Create an array to store all the lat/lang values along our route
       
  4727                 var checkpoints = [];
       
  4728 
       
  4729                 //  Creates a new direction service, later used to create a route
       
  4730                 var directionsService = new google.maps.DirectionsService();
       
  4731 
       
  4732                 //  Creates a new direction renderer using the current map
       
  4733                 //  This enables us to access all of the route data that is returned to us
       
  4734                 var directionsDisplay = new google.maps.DirectionsRenderer( sView2 );
       
  4735 
       
  4736                 var request = {
       
  4737                   origin: options.location,
       
  4738                   destination: options.tween,
       
  4739                   travelMode: google.maps.TravelMode.DRIVING
       
  4740                 };
       
  4741 
       
  4742                 //  Create the route using the direction service and renderer
       
  4743                 directionsService.route( request, function( response, status ) {
       
  4744 
       
  4745                   if ( status == google.maps.DirectionsStatus.OK ) {
       
  4746                     directionsDisplay.setDirections( response );
       
  4747                     showSteps( response, that );
       
  4748                   }
       
  4749 
       
  4750                 });
       
  4751 
       
  4752                 var showSteps = function ( directionResult, that ) {
       
  4753 
       
  4754                   //  Push new google map lat and lng values into an array from our list of lat and lng values
       
  4755                   var routes = directionResult.routes[ 0 ].overview_path;
       
  4756                   for ( var j = 0, k = routes.length; j < k; j++ ) {
       
  4757                     checkpoints.push( new google.maps.LatLng( routes[ j ].lat(), routes[ j ].lng() ) );
       
  4758                   }
       
  4759 
       
  4760                   //  Check to make sure the interval exists, if not, set to a default of 1000
       
  4761                   options.interval = options.interval || 1000;
       
  4762                   tween( checkpoints, 10 );
       
  4763 
       
  4764                 };
       
  4765               } else if ( typeof options.tween === "object" ) {
       
  4766 
       
  4767                 //  Same as the above to stop streetview maps from overflowing into one another
       
  4768                 var sView3 = sView;
       
  4769 
       
  4770                 for ( var i = 0, l = options.tween.length; i < l; i++ ) {
       
  4771 
       
  4772                   //  Make sure interval exists, if not, set to 1000
       
  4773                   options.tween[ i ].interval = options.tween[ i ].interval || 1000;
       
  4774                   tween( options.tween, 10 );
       
  4775                 }
       
  4776               }
       
  4777             }
       
  4778 
       
  4779             if ( options.onmaploaded ) {
       
  4780               options.onmaploaded( options, map );
       
  4781             }
       
  4782 
       
  4783           } else {
       
  4784             setTimeout(function () {
       
  4785               isMapSetup();
       
  4786             }, 13);
       
  4787           }
       
  4788 
       
  4789         };
       
  4790         isMapSetup();
       
  4791       },
       
  4792       /**
       
  4793        * @member webpage
       
  4794        * The end function will be executed when the currentTime
       
  4795        * of the video reaches the end time provided by the
       
  4796        * options variable
       
  4797        */
       
  4798       end: function ( event, options ) {
       
  4799         // if the map exists hide it do not delete the map just in
       
  4800         // case the user seeks back to time b/w start and end
       
  4801         if ( map ) {
       
  4802           map.getDiv().style.display = "none";
       
  4803         }
       
  4804       },
       
  4805       _teardown: function ( options ) {
       
  4806 
       
  4807         var target = document.getElementById( options.target );
       
  4808 
       
  4809         // the map must be manually removed
       
  4810         target && target.removeChild( newdiv );
       
  4811         newdiv = map = location = null;
       
  4812 
       
  4813         options._map = null;
       
  4814       }
       
  4815     };
       
  4816   }, {
       
  4817     about: {
       
  4818       name: "Popcorn Google Map Plugin",
       
  4819       version: "0.1",
       
  4820       author: "@annasob",
       
  4821       website: "annasob.wordpress.com"
       
  4822     },
       
  4823     options: {
       
  4824       start: {
       
  4825         elem: "input",
       
  4826         type: "start",
       
  4827         label: "Start"
       
  4828       },
       
  4829       end: {
       
  4830         elem: "input",
       
  4831         type: "start",
       
  4832         label: "End"
       
  4833       },
       
  4834       target: "map-container",
       
  4835       type: {
       
  4836         elem: "select",
       
  4837         options: [ "ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN", "STAMEN-WATERCOLOR", "STAMEN-TERRAIN", "STAMEN-TONER" ],
       
  4838         label: "Map Type",
       
  4839         optional: true
       
  4840       },
       
  4841       zoom: {
       
  4842         elem: "input",
       
  4843         type: "text",
       
  4844         label: "Zoom",
       
  4845         "default": 0,
       
  4846         optional: true
       
  4847       },
       
  4848       lat: {
       
  4849         elem: "input",
       
  4850         type: "text",
       
  4851         label: "Lat",
       
  4852         optional: true
       
  4853       },
       
  4854       lng: {
       
  4855         elem: "input",
       
  4856         type: "text",
       
  4857         label: "Lng",
       
  4858         optional: true
       
  4859       },
       
  4860       location: {
       
  4861         elem: "input",
       
  4862         type: "text",
       
  4863         label: "Location",
       
  4864         "default": "Toronto, Ontario, Canada"
       
  4865       },
       
  4866       heading: {
       
  4867         elem: "input",
       
  4868         type: "text",
       
  4869         label: "Heading",
       
  4870         "default": 0,
       
  4871         optional: true
       
  4872       },
       
  4873       pitch: {
       
  4874         elem: "input",
       
  4875         type: "text",
       
  4876         label: "Pitch",
       
  4877         "default": 1,
       
  4878         optional: true
       
  4879       }
       
  4880     }
       
  4881   });
       
  4882 })( Popcorn );
       
  4883 
       
  4884 // PLUGIN: IMAGE
       
  4885 
       
  4886 (function ( Popcorn ) {
       
  4887 
       
  4888 /**
       
  4889  * Images popcorn plug-in
       
  4890  * Shows an image element
       
  4891  * Options parameter will need a start, end, href, target and src.
       
  4892  * Start is the time that you want this plug-in to execute
       
  4893  * End is the time that you want this plug-in to stop executing
       
  4894  * href is the url of the destination of a anchor - optional
       
  4895  * Target is the id of the document element that the iframe needs to be attached to,
       
  4896  * this target element must exist on the DOM
       
  4897  * Src is the url of the image that you want to display
       
  4898  * text is the overlayed text on the image - optional
       
  4899  *
       
  4900  * @param {Object} options
       
  4901  *
       
  4902  * Example:
       
  4903    var p = Popcorn('#video')
       
  4904       .image({
       
  4905         start: 5, // seconds
       
  4906         end: 15, // seconds
       
  4907         href: 'http://www.drumbeat.org/',
       
  4908         src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png',
       
  4909         text: 'DRUMBEAT',
       
  4910         target: 'imagediv'
       
  4911       } )
       
  4912  *
       
  4913  */
       
  4914 
       
  4915   var VIDEO_OVERLAY_Z = 2000,
       
  4916       CHECK_INTERVAL_DURATION = 10;
       
  4917 
       
  4918   function trackMediaElement( mediaElement ) {
       
  4919     var checkInterval = -1,
       
  4920         container = document.createElement( "div" ),
       
  4921         videoZ = getComputedStyle( mediaElement ).zIndex;
       
  4922 
       
  4923     container.setAttribute( "data-popcorn-helper-container", true );
       
  4924 
       
  4925     container.style.position = "absolute";
       
  4926 
       
  4927     if ( !isNaN( videoZ ) ) {
       
  4928       container.style.zIndex = videoZ + 1;
       
  4929     }
       
  4930     else {
       
  4931       container.style.zIndex = VIDEO_OVERLAY_Z;
       
  4932     }
       
  4933 
       
  4934     document.body.appendChild( container );
       
  4935 
       
  4936     function check() {
       
  4937       var mediaRect = mediaElement.getBoundingClientRect(),
       
  4938           containerRect = container.getBoundingClientRect();
       
  4939 
       
  4940       if ( containerRect.left !== mediaRect.left ) {
       
  4941         container.style.left = mediaRect.left + "px";
       
  4942       }
       
  4943       if ( containerRect.top !== mediaRect.top ) {
       
  4944         container.style.top = mediaRect.top + "px";
       
  4945       }
       
  4946     }
       
  4947 
       
  4948     return {
       
  4949       element: container,
       
  4950       start: function() {
       
  4951         checkInterval = setInterval( check, CHECK_INTERVAL_DURATION );
       
  4952       },
       
  4953       stop: function() {
       
  4954         clearInterval( checkInterval );
       
  4955         checkInterval = -1;
       
  4956       },
       
  4957       destroy: function() {
       
  4958         document.body.removeChild( container );
       
  4959         if ( checkInterval !== -1 ) {
       
  4960           clearInterval( checkInterval );
       
  4961         }
       
  4962       }
       
  4963     };
       
  4964   }
       
  4965 
       
  4966   Popcorn.plugin( "image", {
       
  4967       manifest: {
       
  4968         about: {
       
  4969           name: "Popcorn image Plugin",
       
  4970           version: "0.1",
       
  4971           author: "Scott Downe",
       
  4972           website: "http://scottdowne.wordpress.com/"
       
  4973         },
       
  4974         options: {
       
  4975           start: {
       
  4976             elem: "input",
       
  4977             type: "number",
       
  4978             label: "Start"
       
  4979           },
       
  4980           end: {
       
  4981             elem: "input",
       
  4982             type: "number",
       
  4983             label: "End"
       
  4984           },
       
  4985           src: {
       
  4986             elem: "input",
       
  4987             type: "url",
       
  4988             label: "Image URL",
       
  4989             "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png"
       
  4990           },
       
  4991           href: {
       
  4992             elem: "input",
       
  4993             type: "url",
       
  4994             label: "Link",
       
  4995             "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png",
       
  4996             optional: true
       
  4997           },
       
  4998           target: "image-container",
       
  4999           text: {
       
  5000             elem: "input",
       
  5001             type: "text",
       
  5002             label: "Caption",
       
  5003             "default": "Popcorn.js",
       
  5004             optional: true
       
  5005           }
       
  5006         }
       
  5007       },
       
  5008       _setup: function( options ) {
       
  5009         var img = document.createElement( "img" ),
       
  5010             target = document.getElementById( options.target );
       
  5011 
       
  5012         options.anchor = document.createElement( "a" );
       
  5013         options.anchor.style.position = "relative";
       
  5014         options.anchor.style.textDecoration = "none";
       
  5015         options.anchor.style.display = "none";
       
  5016 
       
  5017         // add the widget's div to the target div.
       
  5018         // if target is <video> or <audio>, create a container and routinely 
       
  5019         // update its size/position to be that of the media
       
  5020         if ( target ) {
       
  5021           if ( [ "VIDEO", "AUDIO" ].indexOf( target.nodeName ) > -1 ) {
       
  5022             options.trackedContainer = trackMediaElement( target );
       
  5023             options.trackedContainer.element.appendChild( options.anchor );
       
  5024           }
       
  5025           else {
       
  5026             target && target.appendChild( options.anchor );
       
  5027           }          
       
  5028         }
       
  5029 
       
  5030         img.addEventListener( "load", function() {
       
  5031 
       
  5032           // borders look really bad, if someone wants it they can put it on their div target
       
  5033           img.style.borderStyle = "none";
       
  5034 
       
  5035           options.anchor.href = options.href || options.src || "#";
       
  5036           options.anchor.target = "_blank";
       
  5037 
       
  5038           var fontHeight, divText;
       
  5039 
       
  5040           img.style.height = target.style.height;
       
  5041           img.style.width = target.style.width;
       
  5042 
       
  5043           options.anchor.appendChild( img );
       
  5044 
       
  5045           // If display text was provided, display it:
       
  5046           if ( options.text ) {
       
  5047             fontHeight = ( img.height / 12 ) + "px";
       
  5048             divText = document.createElement( "div" );
       
  5049 
       
  5050             Popcorn.extend( divText.style, {
       
  5051               color: "black",
       
  5052               fontSize: fontHeight,
       
  5053               fontWeight: "bold",
       
  5054               position: "relative",
       
  5055               textAlign: "center",
       
  5056               width: img.style.width || img.width + "px",
       
  5057               zIndex: "10"
       
  5058             });
       
  5059 
       
  5060             divText.innerHTML = options.text || "";
       
  5061 
       
  5062             divText.style.top = ( ( img.style.height.replace( "px", "" ) || img.height ) / 2 ) - ( divText.offsetHeight / 2 ) + "px";
       
  5063             options.anchor.insertBefore( divText, img );
       
  5064           }
       
  5065         }, false );
       
  5066 
       
  5067         img.src = options.src;
       
  5068       },
       
  5069 
       
  5070       /**
       
  5071        * @member image
       
  5072        * The start function will be executed when the currentTime
       
  5073        * of the video  reaches the start time provided by the
       
  5074        * options variable
       
  5075        */
       
  5076       start: function( event, options ) {
       
  5077         options.anchor.style.display = "inline";
       
  5078         if ( options.trackedContainer ) {
       
  5079           options.trackedContainer.start();
       
  5080         }
       
  5081       },
       
  5082       /**
       
  5083        * @member image
       
  5084        * The end function will be executed when the currentTime
       
  5085        * of the video  reaches the end time provided by the
       
  5086        * options variable
       
  5087        */
       
  5088       end: function( event, options ) {
       
  5089         options.anchor.style.display = "none";
       
  5090         if ( options.trackedContainer ) {
       
  5091           options.trackedContainer.stop();
       
  5092         }
       
  5093       },
       
  5094       _teardown: function( options ) {
       
  5095         if ( options.trackedContainer ) {
       
  5096           options.trackedContainer.destroy();
       
  5097         }
       
  5098         else if ( options.anchor.parentNode ) {
       
  5099           options.anchor.parentNode.removeChild( options.anchor );
       
  5100         }
       
  5101       }
       
  5102   });
       
  5103 })( Popcorn );
       
  5104 // PLUGIN: Google Feed
       
  5105 (function ( Popcorn ) {
       
  5106 
       
  5107   var i = 1,
       
  5108       scriptLoaded  = false;
       
  5109 
       
  5110   /**
       
  5111    * googlefeed popcorn plug-in
       
  5112    * Adds a feed from the specified blog url at the target div
       
  5113    * Options parameter will need a start, end, target, url and title
       
  5114    * -Start is the time that you want this plug-in to execute
       
  5115    * -End is the time that you want this plug-in to stop executing
       
  5116    * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
       
  5117    * -Url is the url of the blog's feed you are trying to access
       
  5118    * -Title is the title of the blog you want displayed above the feed
       
  5119    * -Orientation is the orientation of the blog, accepts either Horizontal or Vertical, defaults to Vertical
       
  5120    * @param {Object} options
       
  5121    *
       
  5122    * Example:
       
  5123     var p = Popcorn("#video")
       
  5124       .googlefeed({
       
  5125        start: 5, // seconds
       
  5126        end: 15, // seconds
       
  5127        target: "map",
       
  5128        url: "http://zenit.senecac.on.ca/~chris.tyler/planet/rss20.xml",
       
  5129        title: "Planet Feed"
       
  5130     } )
       
  5131   *
       
  5132   */
       
  5133 
       
  5134   Popcorn.plugin( "googlefeed", function( options ) {
       
  5135 
       
  5136     var dynamicFeedLoad = function() {
       
  5137       var dontLoad = false,
       
  5138           k = 0,
       
  5139           links = document.getElementsByTagName( "link" ),
       
  5140           len = links.length,
       
  5141           head = document.head || document.getElementsByTagName( "head" )[ 0 ],
       
  5142           css = document.createElement( "link" ),
       
  5143           resource = "//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.";
       
  5144 
       
  5145       if ( !window.GFdynamicFeedControl ) {
       
  5146 
       
  5147         Popcorn.getScript( resource + "js", function() {
       
  5148           scriptLoaded = true;
       
  5149         });
       
  5150 
       
  5151       } else {
       
  5152         scriptLoaded = true;
       
  5153       }
       
  5154 
       
  5155       //  Checking if the css file is already included
       
  5156       for ( ; k < len; k++ ){
       
  5157         if ( links[ k ].href === resource + "css" ) {
       
  5158           dontLoad = true;
       
  5159         }
       
  5160       }
       
  5161 
       
  5162       if ( !dontLoad ) {
       
  5163         css.type = "text/css";
       
  5164         css.rel = "stylesheet";
       
  5165         css.href =  resource + "css";
       
  5166         head.insertBefore( css, head.firstChild );
       
  5167       }
       
  5168     };
       
  5169 
       
  5170     if ( !window.google ) {
       
  5171 
       
  5172       Popcorn.getScript( "//www.google.com/jsapi", function() {
       
  5173 
       
  5174         google.load( "feeds", "1", {
       
  5175 
       
  5176           callback: function () {
       
  5177 
       
  5178             dynamicFeedLoad();
       
  5179           }
       
  5180         });
       
  5181       });
       
  5182 
       
  5183     } else {
       
  5184       dynamicFeedLoad();
       
  5185     }
       
  5186 
       
  5187     // create a new div and append it to the parent div so nothing
       
  5188     // that already exists in the parent div gets overwritten
       
  5189     var newdiv = document.createElement( "div" ),
       
  5190         target = document.getElementById( options.target ),
       
  5191     initialize = function() {
       
  5192       //ensure that the script has been loaded
       
  5193       if ( !scriptLoaded ) {
       
  5194         setTimeout( function () {
       
  5195           initialize();
       
  5196         }, 5 );
       
  5197       } else {
       
  5198         // Create the feed control using the user entered url and title
       
  5199         options.feed = new GFdynamicFeedControl( options.url, newdiv, {
       
  5200           vertical: options.orientation.toLowerCase() === "vertical" ? true : false,
       
  5201           horizontal: options.orientation.toLowerCase() === "horizontal" ? true : false,
       
  5202           title: options.title = options.title || "Blog"
       
  5203         });
       
  5204       }
       
  5205     };
       
  5206 
       
  5207     // Default to vertical orientation if empty or incorrect input
       
  5208     if( !options.orientation || ( options.orientation.toLowerCase() !== "vertical" &&
       
  5209       options.orientation.toLowerCase() !== "horizontal" ) ) {
       
  5210       options.orientation = "vertical";
       
  5211     }
       
  5212 
       
  5213     newdiv.style.display = "none";
       
  5214     newdiv.id = "_feed" + i;
       
  5215     newdiv.style.width = "100%";
       
  5216     newdiv.style.height = "100%";
       
  5217     i++;
       
  5218 
       
  5219     target && target.appendChild( newdiv );
       
  5220 
       
  5221     initialize();
       
  5222 
       
  5223     return {
       
  5224       /**
       
  5225        * @member webpage
       
  5226        * The start function will be executed when the currentTime
       
  5227        * of the video reaches the start time provided by the
       
  5228        * options variable
       
  5229        */
       
  5230       start: function( event, options ){
       
  5231         newdiv.setAttribute( "style", "display:inline" );
       
  5232       },
       
  5233       /**
       
  5234        * @member webpage
       
  5235        * The end function will be executed when the currentTime
       
  5236        * of the video reaches the end time provided by the
       
  5237        * options variable
       
  5238        */
       
  5239       end: function( event, options ){
       
  5240         newdiv.setAttribute( "style", "display:none" );
       
  5241       },
       
  5242       _teardown: function( options ) {
       
  5243         document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( newdiv );
       
  5244         delete options.feed;
       
  5245       }
       
  5246     };
       
  5247   },
       
  5248   {
       
  5249     about: {
       
  5250       name: "Popcorn Google Feed Plugin",
       
  5251       version: "0.1",
       
  5252       author: "David Seifried",
       
  5253       website: "dseifried.wordpress.com"
       
  5254     },
       
  5255     options: {
       
  5256       start: {
       
  5257         elem: "input",
       
  5258         type: "number",
       
  5259         label: "Start"
       
  5260       },
       
  5261       end: {
       
  5262         elem: "input",
       
  5263         type: "number",
       
  5264         label: "End"
       
  5265       },
       
  5266       target: "feed-container",
       
  5267       url: {
       
  5268         elem: "input",
       
  5269         type: "url",
       
  5270         label: "Feed URL",
       
  5271         "default": "http://planet.mozilla.org/rss20.xml"
       
  5272       },
       
  5273       title: {
       
  5274         elem: "input",
       
  5275         type: "text",
       
  5276         label: "Title",
       
  5277         "default": "Planet Mozilla",
       
  5278         optional: true
       
  5279       },
       
  5280       orientation: {
       
  5281         elem: "select",
       
  5282         options: [ "Vertical", "Horizontal" ],
       
  5283         label: "Orientation",
       
  5284         "default": "Vertical",
       
  5285         optional: true
       
  5286       }
       
  5287     }
       
  5288   });
       
  5289 })( Popcorn );
       
  5290 // PLUGIN: Subtitle
       
  5291 
       
  5292 (function ( Popcorn ) {
       
  5293 
       
  5294   var i = 0,
       
  5295       createDefaultContainer = function( context, id ) {
       
  5296 
       
  5297         var ctxContainer = context.container = document.createElement( "div" ),
       
  5298             style = ctxContainer.style,
       
  5299             media = context.media;
       
  5300 
       
  5301         var updatePosition = function() {
       
  5302           var position = context.position();
       
  5303           // the video element must have height and width defined
       
  5304           style.fontSize = "18px";
       
  5305           style.width = media.offsetWidth + "px";
       
  5306           style.top = position.top  + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px";
       
  5307           style.left = position.left + "px";
       
  5308 
       
  5309           setTimeout( updatePosition, 10 );
       
  5310         };
       
  5311 
       
  5312         ctxContainer.id = id || Popcorn.guid();
       
  5313         style.position = "absolute";
       
  5314         style.color = "white";
       
  5315         style.textShadow = "black 2px 2px 6px";
       
  5316         style.fontWeight = "bold";
       
  5317         style.textAlign = "center";
       
  5318 
       
  5319         updatePosition();
       
  5320 
       
  5321         context.media.parentNode.appendChild( ctxContainer );
       
  5322 
       
  5323         return ctxContainer;
       
  5324       };
       
  5325 
       
  5326   /**
       
  5327    * Subtitle popcorn plug-in
       
  5328    * Displays a subtitle over the video, or in the target div
       
  5329    * Options parameter will need a start, and end.
       
  5330    * Optional parameters are target and text.
       
  5331    * Start is the time that you want this plug-in to execute
       
  5332    * End is the time that you want this plug-in to stop executing
       
  5333    * Target is the id of the document element that the content is
       
  5334    *  appended to, this target element must exist on the DOM
       
  5335    * Text is the text of the subtitle you want to display.
       
  5336    *
       
  5337    * @param {Object} options
       
  5338    *
       
  5339    * Example:
       
  5340      var p = Popcorn('#video')
       
  5341         .subtitle({
       
  5342           start:            5,                 // seconds, mandatory
       
  5343           end:              15,                // seconds, mandatory
       
  5344           text:             'Hellow world',    // optional
       
  5345           target:           'subtitlediv',     // optional
       
  5346         } )
       
  5347    *
       
  5348    */
       
  5349 
       
  5350   Popcorn.plugin( "subtitle" , {
       
  5351 
       
  5352       manifest: {
       
  5353         about: {
       
  5354           name: "Popcorn Subtitle Plugin",
       
  5355           version: "0.1",
       
  5356           author: "Scott Downe",
       
  5357           website: "http://scottdowne.wordpress.com/"
       
  5358         },
       
  5359         options: {
       
  5360           start: {
       
  5361             elem: "input",
       
  5362             type: "text",
       
  5363             label: "Start"
       
  5364           },
       
  5365           end: {
       
  5366             elem: "input",
       
  5367             type: "text",
       
  5368             label: "End"
       
  5369           },
       
  5370           target: "subtitle-container",
       
  5371           text: {
       
  5372             elem: "input",
       
  5373             type: "text",
       
  5374             label: "Text"
       
  5375           }
       
  5376         }
       
  5377       },
       
  5378 
       
  5379       _setup: function( options ) {
       
  5380         var newdiv = document.createElement( "div" );
       
  5381 
       
  5382         newdiv.id = "subtitle-" + i++;
       
  5383         newdiv.style.display = "none";
       
  5384 
       
  5385         // Creates a div for all subtitles to use
       
  5386         ( !this.container && ( !options.target || options.target === "subtitle-container" ) ) &&
       
  5387           createDefaultContainer( this );
       
  5388 
       
  5389         // if a target is specified, use that
       
  5390         if ( options.target && options.target !== "subtitle-container" ) {
       
  5391           // In case the target doesn't exist in the DOM
       
  5392           options.container = document.getElementById( options.target ) || createDefaultContainer( this, options.target );
       
  5393         } else {
       
  5394           // use shared default container
       
  5395           options.container = this.container;
       
  5396         }
       
  5397 
       
  5398         document.getElementById( options.container.id ) && document.getElementById( options.container.id ).appendChild( newdiv );
       
  5399         options.innerContainer = newdiv;
       
  5400 
       
  5401         options.showSubtitle = function() {
       
  5402           options.innerContainer.innerHTML = options.text || "";
       
  5403         };
       
  5404       },
       
  5405       /**
       
  5406        * @member subtitle
       
  5407        * The start function will be executed when the currentTime
       
  5408        * of the video  reaches the start time provided by the
       
  5409        * options variable
       
  5410        */
       
  5411       start: function( event, options ){
       
  5412         options.innerContainer.style.display = "inline";
       
  5413         options.showSubtitle( options, options.text );
       
  5414       },
       
  5415       /**
       
  5416        * @member subtitle
       
  5417        * The end function will be executed when the currentTime
       
  5418        * of the video  reaches the end time provided by the
       
  5419        * options variable
       
  5420        */
       
  5421       end: function( event, options ) {
       
  5422         options.innerContainer.style.display = "none";
       
  5423         options.innerContainer.innerHTML = "";
       
  5424       },
       
  5425 
       
  5426       _teardown: function ( options ) {
       
  5427         options.container.removeChild( options.innerContainer );
       
  5428       }
       
  5429 
       
  5430   });
       
  5431 
       
  5432 })( Popcorn );
       
  5433 // PLUGIN: TWITTER
       
  5434 
       
  5435 (function ( Popcorn ) {
       
  5436   var scriptLoading = false;
       
  5437 
       
  5438   /**
       
  5439    * Twitter popcorn plug-in
       
  5440    * Appends a Twitter widget to an element on the page.
       
  5441    * Options parameter will need a start, end, target and source.
       
  5442    * Optional parameters are height and width.
       
  5443    * Start is the time that you want this plug-in to execute
       
  5444    * End is the time that you want this plug-in to stop executing
       
  5445    * Src is the hash tag or twitter user to get tweets from
       
  5446    * Target is the id of the document element that the images are
       
  5447    *  appended to, this target element must exist on the DOM
       
  5448    * Height is the height of the widget, defaults to 200
       
  5449    * Width is the width of the widget, defaults to 250
       
  5450    *
       
  5451    * @param {Object} options
       
  5452    *
       
  5453    * Example:
       
  5454      var p = Popcorn('#video')
       
  5455         .twitter({
       
  5456           start:          5,                // seconds, mandatory
       
  5457           end:            15,               // seconds, mandatory
       
  5458           src:            '@stevesong',     // mandatory, also accepts hash tags
       
  5459           height:         200,              // optional
       
  5460           width:          250,              // optional
       
  5461           target:         'twitterdiv'      // mandatory
       
  5462         } )
       
  5463    *
       
  5464    */
       
  5465 
       
  5466   Popcorn.plugin( "twitter" , {
       
  5467 
       
  5468       manifest: {
       
  5469         about: {
       
  5470           name: "Popcorn Twitter Plugin",
       
  5471           version: "0.1",
       
  5472           author: "Scott Downe",
       
  5473           website: "http://scottdowne.wordpress.com/"
       
  5474         },
       
  5475         options:{
       
  5476           start: {
       
  5477             elem: "input",
       
  5478             type: "number",
       
  5479             label: "Start"
       
  5480           },
       
  5481           end: {
       
  5482             elem: "input",
       
  5483             type: "number",
       
  5484             label: "End"
       
  5485           },
       
  5486           src: {
       
  5487             elem: "input",
       
  5488             type: "text",
       
  5489             label: "Tweet Source (# or @)",
       
  5490             "default": "@popcornjs"
       
  5491           },
       
  5492           target: "twitter-container",
       
  5493           height: {
       
  5494             elem: "input",
       
  5495             type: "number",
       
  5496             label: "Height",
       
  5497             "default": "200",
       
  5498             optional: true
       
  5499           },
       
  5500           width: {
       
  5501             elem: "input",
       
  5502             type: "number",
       
  5503             label: "Width",
       
  5504             "default": "250",
       
  5505             optional: true
       
  5506           }
       
  5507         }
       
  5508       },
       
  5509 
       
  5510       _setup: function( options ) {
       
  5511 
       
  5512         if ( !window.TWTR && !scriptLoading ) {
       
  5513           scriptLoading = true;
       
  5514           Popcorn.getScript( "//widgets.twimg.com/j/2/widget.js" );
       
  5515         }
       
  5516 
       
  5517         var target = document.getElementById( options.target );
       
  5518         // create the div to store the widget
       
  5519         // setup widget div that is unique per track
       
  5520         options.container = document.createElement( "div" );
       
  5521         // use this id to connect it to the widget
       
  5522         options.container.setAttribute( "id", Popcorn.guid() );
       
  5523         // display none by default
       
  5524         options.container.style.display = "none";
       
  5525 
       
  5526          // add the widget's div to the target div
       
  5527         target && target.appendChild( options.container );
       
  5528 
       
  5529         // setup info for the widget
       
  5530         var src = options.src || "",
       
  5531             width = options.width || 250,
       
  5532             height = options.height || 200,
       
  5533             profile = /^@/.test( src ),
       
  5534             widgetOptions = {
       
  5535               version: 2,
       
  5536               // use this id to connect it to the div
       
  5537               id: options.container.getAttribute( "id" ),
       
  5538               rpp: 30,
       
  5539               width: width,
       
  5540               height: height,
       
  5541               interval: 6000,
       
  5542               theme: {
       
  5543                 shell: {
       
  5544                   background: "#ffffff",
       
  5545                   color: "#000000"
       
  5546                 },
       
  5547                 tweets: {
       
  5548                   background: "#ffffff",
       
  5549                   color: "#444444",
       
  5550                   links: "#1985b5"
       
  5551                 }
       
  5552               },
       
  5553               features: {
       
  5554                 loop: true,
       
  5555                 timestamp: true,
       
  5556                 avatars: true,
       
  5557                 hashtags: true,
       
  5558                 toptweets: true,
       
  5559                 live: true,
       
  5560                 scrollbar: false,
       
  5561                 behavior: 'default'
       
  5562               }
       
  5563             };
       
  5564 
       
  5565         // create widget
       
  5566         var isReady = function( that ) {
       
  5567           if ( window.TWTR ) {
       
  5568             if ( profile ) {
       
  5569 
       
  5570               widgetOptions.type = "profile";
       
  5571 
       
  5572               new TWTR.Widget( widgetOptions ).render().setUser( src ).start();
       
  5573 
       
  5574             } else {
       
  5575 
       
  5576               widgetOptions.type = "search";
       
  5577               widgetOptions.search = src;
       
  5578               widgetOptions.subject = src;
       
  5579 
       
  5580               new TWTR.Widget( widgetOptions ).render().start();
       
  5581 
       
  5582             }
       
  5583           } else {
       
  5584             setTimeout( function() {
       
  5585               isReady( that );
       
  5586             }, 1);
       
  5587           }
       
  5588         };
       
  5589 
       
  5590         isReady( this );
       
  5591       },
       
  5592 
       
  5593       /**
       
  5594        * @member Twitter
       
  5595        * The start function will be executed when the currentTime
       
  5596        * of the video  reaches the start time provided by the
       
  5597        * options variable
       
  5598        */
       
  5599       start: function( event, options ) {
       
  5600         options.container.style.display = "inline";
       
  5601       },
       
  5602 
       
  5603       /**
       
  5604        * @member Twitter
       
  5605        * The end function will be executed when the currentTime
       
  5606        * of the video  reaches the end time provided by the
       
  5607        * options variable
       
  5608        */
       
  5609       end: function( event, options ) {
       
  5610         options.container.style.display = "none";
       
  5611       },
       
  5612       _teardown: function( options ) {
       
  5613 
       
  5614         document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
       
  5615       }
       
  5616     });
       
  5617 
       
  5618 })( Popcorn );
       
  5619 // PLUGIN: WEBPAGE
       
  5620 
       
  5621 (function ( Popcorn ) {
       
  5622 
       
  5623   /**
       
  5624    * Webpages popcorn plug-in
       
  5625    * Creates an iframe showing a website specified by the user
       
  5626    * Options parameter will need a start, end, id, target and src.
       
  5627    * Start is the time that you want this plug-in to execute
       
  5628    * End is the time that you want this plug-in to stop executing
       
  5629    * Id is the id that you want assigned to the iframe
       
  5630    * Target is the id of the document element that the iframe needs to be attached to,
       
  5631    * this target element must exist on the DOM
       
  5632    * Src is the url of the website that you want the iframe to display
       
  5633    *
       
  5634    * @param {Object} options
       
  5635    *
       
  5636    * Example:
       
  5637      var p = Popcorn('#video')
       
  5638         .webpage({
       
  5639           id: "webpages-a",
       
  5640           start: 5, // seconds
       
  5641           end: 15, // seconds
       
  5642           src: 'http://www.webmademovies.org',
       
  5643           target: 'webpagediv'
       
  5644         } )
       
  5645    *
       
  5646    */
       
  5647   Popcorn.plugin( "webpage" , {
       
  5648     manifest: {
       
  5649       about: {
       
  5650         name: "Popcorn Webpage Plugin",
       
  5651         version: "0.1",
       
  5652         author: "@annasob",
       
  5653         website: "annasob.wordpress.com"
       
  5654       },
       
  5655       options: {
       
  5656         id: {
       
  5657           elem: "input",
       
  5658           type: "text",
       
  5659           label: "Id",
       
  5660           optional: true
       
  5661         },
       
  5662         start: {
       
  5663           elem: "input",
       
  5664           type: "number",
       
  5665           label: "Start"
       
  5666         },
       
  5667         end: {
       
  5668           elem: "input",
       
  5669           type: "number",
       
  5670           label: "End"
       
  5671         },
       
  5672         src: {
       
  5673           elem: "input",
       
  5674           type: "url",
       
  5675           label: "Webpage URL",
       
  5676           "default": "http://mozillapopcorn.org"
       
  5677         },
       
  5678         target: "iframe-container"
       
  5679       }
       
  5680     },
       
  5681     _setup: function( options ) {
       
  5682 
       
  5683       var target = document.getElementById( options.target );
       
  5684 
       
  5685       // make src an iframe acceptable string
       
  5686       options.src = options.src.replace( /^(https?:)?(\/\/)?/, "//" );
       
  5687 
       
  5688       // make an iframe
       
  5689       options._iframe = document.createElement( "iframe" );
       
  5690       options._iframe.setAttribute( "width", "100%" );
       
  5691       options._iframe.setAttribute( "height", "100%" );
       
  5692       options._iframe.id = options.id;
       
  5693       options._iframe.src = options.src;
       
  5694       options._iframe.style.display = "none";
       
  5695 
       
  5696       // add the hidden iframe to the DOM
       
  5697       target && target.appendChild( options._iframe );
       
  5698 
       
  5699     },
       
  5700     /**
       
  5701      * @member webpage
       
  5702      * The start function will be executed when the currentTime
       
  5703      * of the video  reaches the start time provided by the
       
  5704      * options variable
       
  5705      */
       
  5706     start: function( event, options ){
       
  5707       // make the iframe visible
       
  5708       options._iframe.src = options.src;
       
  5709       options._iframe.style.display = "inline";
       
  5710     },
       
  5711     /**
       
  5712      * @member webpage
       
  5713      * The end function will be executed when the currentTime
       
  5714      * of the video  reaches the end time provided by the
       
  5715      * options variable
       
  5716      */
       
  5717     end: function( event, options ){
       
  5718       // make the iframe invisible
       
  5719       options._iframe.style.display = "none";
       
  5720     },
       
  5721     _teardown: function( options ) {
       
  5722 
       
  5723       document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._iframe );
       
  5724     }
       
  5725   });
       
  5726 })( Popcorn );
       
  5727 // PLUGIN: WIKIPEDIA
       
  5728 
       
  5729 
       
  5730 var wikiCallback;
       
  5731 
       
  5732 (function ( Popcorn ) {
       
  5733 
       
  5734   /**
       
  5735    * Wikipedia popcorn plug-in
       
  5736    * Displays a wikipedia aricle in the target specified by the user by using
       
  5737    * new DOM element instead overwriting them
       
  5738    * Options parameter will need a start, end, target, lang, src, title and numberofwords.
       
  5739    * -Start is the time that you want this plug-in to execute
       
  5740    * -End is the time that you want this plug-in to stop executing
       
  5741    * -Target is the id of the document element that the text from the article needs to be
       
  5742    * attached to, this target element must exist on the DOM
       
  5743    * -Lang (optional, defaults to english) is the language in which the article is in.
       
  5744    * -Src is the url of the article
       
  5745    * -Title (optional) is the title of the article
       
  5746    * -numberofwords (optional, defaults to 200) is  the number of words you want displaid.
       
  5747    *
       
  5748    * @param {Object} options
       
  5749    *
       
  5750    * Example:
       
  5751      var p = Popcorn("#video")
       
  5752         .wikipedia({
       
  5753           start: 5, // seconds
       
  5754           end: 15, // seconds
       
  5755           src: "http://en.wikipedia.org/wiki/Cape_Town",
       
  5756           target: "wikidiv"
       
  5757         } )
       
  5758    *
       
  5759    */
       
  5760   Popcorn.plugin( "wikipedia" , {
       
  5761 
       
  5762     manifest: {
       
  5763       about:{
       
  5764         name: "Popcorn Wikipedia Plugin",
       
  5765         version: "0.1",
       
  5766         author: "@annasob",
       
  5767         website: "annasob.wordpress.com"
       
  5768       },
       
  5769       options:{
       
  5770         start: {
       
  5771           elem: "input",
       
  5772           type: "number",
       
  5773           label: "Start"
       
  5774         },
       
  5775         end: {
       
  5776           elem: "input",
       
  5777           type: "number",
       
  5778           label: "End"
       
  5779         },
       
  5780         lang: {
       
  5781           elem: "input",
       
  5782           type: "text",
       
  5783           label: "Language",
       
  5784           "default": "english",
       
  5785           optional: true
       
  5786         },
       
  5787         src: {
       
  5788           elem: "input", 
       
  5789           type: "url", 
       
  5790           label: "Wikipedia URL",
       
  5791           "default": "http://en.wikipedia.org/wiki/Cat"
       
  5792         },
       
  5793         title: {
       
  5794           elem: "input",
       
  5795           type: "text",
       
  5796           label: "Title",
       
  5797           "default": "Cats",
       
  5798           optional: true
       
  5799         },
       
  5800         numberofwords: {
       
  5801           elem: "input",
       
  5802           type: "number",
       
  5803           label: "Number of Words",
       
  5804           "default": "200",
       
  5805           optional: true
       
  5806         },
       
  5807         target: "wikipedia-container"
       
  5808       }
       
  5809     },
       
  5810     /**
       
  5811      * @member wikipedia
       
  5812      * The setup function will get all of the needed
       
  5813      * items in place before the start function is called.
       
  5814      * This includes getting data from wikipedia, if the data
       
  5815      * is not received and processed before start is called start
       
  5816      * will not do anything
       
  5817      */
       
  5818     _setup : function( options ) {
       
  5819       // declare needed variables
       
  5820       // get a guid to use for the global wikicallback function
       
  5821       var  _text, _guid = Popcorn.guid();
       
  5822 
       
  5823       // if the user didn't specify a language default to english
       
  5824       if ( !options.lang ) {
       
  5825         options.lang = "en";
       
  5826       }
       
  5827 
       
  5828       // if the user didn't specify number of words to use default to 200
       
  5829       options.numberofwords  = options.numberofwords || 200;
       
  5830 
       
  5831       // wiki global callback function with a unique id
       
  5832       // function gets the needed information from wikipedia
       
  5833       // and stores it by appending values to the options object
       
  5834       window[ "wikiCallback" + _guid ]  = function ( data ) {
       
  5835 
       
  5836         options._link = document.createElement( "a" );
       
  5837         options._link.setAttribute( "href", options.src );
       
  5838         options._link.setAttribute( "target", "_blank" );
       
  5839 
       
  5840         // add the title of the article to the link
       
  5841         options._link.innerHTML = options.title || data.parse.displaytitle;
       
  5842 
       
  5843         // get the content of the wiki article
       
  5844         options._desc = document.createElement( "p" );
       
  5845 
       
  5846         // get the article text and remove any special characters
       
  5847         _text = data.parse.text[ "*" ].substr( data.parse.text[ "*" ].indexOf( "<p>" ) );
       
  5848         _text = _text.replace( /((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g, "" );
       
  5849 
       
  5850         _text = _text.split( " " );
       
  5851         options._desc.innerHTML = ( _text.slice( 0, ( _text.length >= options.numberofwords ? options.numberofwords : _text.length ) ).join (" ") + " ..." ) ;
       
  5852 
       
  5853         options._fired = true;
       
  5854       };
       
  5855 
       
  5856       if ( options.src ) {
       
  5857         Popcorn.getScript( "//" + options.lang + ".wikipedia.org/w/api.php?action=parse&props=text&redirects&page=" +
       
  5858           options.src.slice( options.src.lastIndexOf( "/" ) + 1 )  + "&format=json&callback=wikiCallback" + _guid );
       
  5859       }
       
  5860 
       
  5861     },
       
  5862     /**
       
  5863      * @member wikipedia
       
  5864      * The start function will be executed when the currentTime
       
  5865      * of the video  reaches the start time provided by the
       
  5866      * options variable
       
  5867      */
       
  5868     start: function( event, options ){
       
  5869       // dont do anything if the information didn't come back from wiki
       
  5870       var isReady = function () {
       
  5871 
       
  5872         if ( !options._fired ) {
       
  5873           setTimeout( function () {
       
  5874             isReady();
       
  5875           }, 13);
       
  5876         } else {
       
  5877 
       
  5878           if ( options._link && options._desc ) {
       
  5879             if ( document.getElementById( options.target ) ) {
       
  5880               document.getElementById( options.target ).appendChild( options._link );
       
  5881               document.getElementById( options.target ).appendChild( options._desc );
       
  5882               options._added = true;
       
  5883             }
       
  5884           }
       
  5885         }
       
  5886       };
       
  5887 
       
  5888       isReady();
       
  5889     },
       
  5890     /**
       
  5891      * @member wikipedia
       
  5892      * The end function will be executed when the currentTime
       
  5893      * of the video  reaches the end time provided by the
       
  5894      * options variable
       
  5895      */
       
  5896     end: function( event, options ){
       
  5897       // ensure that the data was actually added to the
       
  5898       // DOM before removal
       
  5899       if ( options._added ) {
       
  5900         document.getElementById( options.target ).removeChild( options._link );
       
  5901         document.getElementById( options.target ).removeChild( options._desc );
       
  5902       }
       
  5903     },
       
  5904 
       
  5905     _teardown: function( options ){
       
  5906 
       
  5907       if ( options._added ) {
       
  5908         options._link.parentNode && document.getElementById( options.target ).removeChild( options._link );
       
  5909         options._desc.parentNode && document.getElementById( options.target ).removeChild( options._desc );
       
  5910         delete options.target;
       
  5911       }
       
  5912     }
       
  5913   });
       
  5914 
       
  5915 })( Popcorn );
       
  5916 // PLUGIN: Mustache
       
  5917 
       
  5918 (function ( Popcorn ) {
       
  5919 
       
  5920   /**
       
  5921    * Mustache Popcorn Plug-in
       
  5922    *
       
  5923    * Adds the ability to render JSON using templates via the Mustache templating library.
       
  5924    *
       
  5925    * @param {Object} options
       
  5926    *
       
  5927    * Required parameters: start, end, template, data, and target.
       
  5928    * Optional parameter: static.
       
  5929    *
       
  5930    *   start: the time in seconds when the mustache template should be rendered
       
  5931    *          in the target div.
       
  5932    *
       
  5933    *   end: the time in seconds when the rendered mustache template should be
       
  5934    *        removed from the target div.
       
  5935    *
       
  5936    *   target: a String -- the target div's id.
       
  5937    *
       
  5938    *   template: the mustache template for the plugin to use when rendering.  This can be
       
  5939    *             a String containing the template, or a Function that returns the template's
       
  5940    *             String.
       
  5941    *
       
  5942    *   data: the data to be rendered using the mustache template.  This can be a JSON String,
       
  5943    *         a JavaScript Object literal, or a Function returning a String or Literal.
       
  5944    *
       
  5945    *   dynamic: an optional argument indicating that the template and json data are dynamic
       
  5946    *            and need to be loaded dynamically on every use.  Defaults to True.
       
  5947    *
       
  5948    * Example:
       
  5949      var p = Popcorn('#video')
       
  5950 
       
  5951         // Example using template and JSON strings.
       
  5952         .mustache({
       
  5953           start: 5, // seconds
       
  5954           end:  15,  // seconds
       
  5955           target: 'mustache',
       
  5956           template: '<h1>{{header}}</h1>'                         +
       
  5957                     '{{#bug}}'                                    +
       
  5958                     '{{/bug}}'                                    +
       
  5959                     ''                                            +
       
  5960                     '{{#items}}'                                  +
       
  5961                     '  {{#first}}'                                +
       
  5962                     '    <li><strong>{{name}}</strong></li>'      +
       
  5963                     '  {{/first}}'                                +
       
  5964                     '  {{#link}}'                                 +
       
  5965                     '    <li><a href="{{url}}">{{name}}</a></li>' +
       
  5966                     '  {{/link}}'                                 +
       
  5967                     '{{/items}}'                                  +
       
  5968                     ''                                            +
       
  5969                     '{{#empty}}'                                  +
       
  5970                     '  <p>The list is empty.</p>'                 +
       
  5971                     '{{/empty}}'                                  ,
       
  5972 
       
  5973           data:     '{'                                                        +
       
  5974                     '  "header": "Colors", '                                   +
       
  5975                     '  "items": [ '                                            +
       
  5976                     '      {"name": "red", "first": true, "url": "#Red"}, '    +
       
  5977                     '      {"name": "green", "link": true, "url": "#Green"}, ' +
       
  5978                     '      {"name": "blue", "link": true, "url": "#Blue"} '    +
       
  5979                     '  ],'                                                     +
       
  5980                     '  'empty': false'                                         +
       
  5981                     '}',
       
  5982           dynamic: false // The json is not going to change, load it early.
       
  5983         } )
       
  5984 
       
  5985         // Example showing Functions instead of Strings.
       
  5986         .mustache({
       
  5987           start: 20,  // seconds
       
  5988           end:   25,  // seconds
       
  5989           target: 'mustache',
       
  5990           template: function(instance, options) {
       
  5991                       var template = // load your template file here...
       
  5992                       return template;
       
  5993                     },
       
  5994           data:     function(instance, options) {
       
  5995                       var json = // load your json here...
       
  5996                       return json;
       
  5997                     }
       
  5998         } );
       
  5999   *
       
  6000   */
       
  6001 
       
  6002   Popcorn.plugin( "mustache" , function( options ){
       
  6003 
       
  6004     var getData, data, getTemplate, template;
       
  6005 
       
  6006     Popcorn.getScript( "http://mustache.github.com/extras/mustache.js" );
       
  6007 
       
  6008     var shouldReload = !!options.dynamic,
       
  6009         typeOfTemplate = typeof options.template,
       
  6010         typeOfData = typeof options.data,
       
  6011         target = document.getElementById( options.target );
       
  6012 
       
  6013     options.container = target || document.createElement( "div" );
       
  6014 
       
  6015     if ( typeOfTemplate === "function" ) {
       
  6016       if ( !shouldReload ) {
       
  6017         template = options.template( options );
       
  6018       } else {
       
  6019         getTemplate = options.template;
       
  6020       }
       
  6021     } else if ( typeOfTemplate === "string" ) {
       
  6022       template = options.template;
       
  6023     } else {
       
  6024       template = "";
       
  6025     }
       
  6026 
       
  6027     if ( typeOfData === "function" ) {
       
  6028       if ( !shouldReload ) {
       
  6029         data = options.data( options );
       
  6030       } else {
       
  6031         getData = options.data;
       
  6032       }
       
  6033     } else if ( typeOfData === "string" ) {
       
  6034       data = JSON.parse( options.data );
       
  6035     } else if ( typeOfData === "object" ) {
       
  6036       data = options.data;
       
  6037     } else {
       
  6038       data = "";
       
  6039     }
       
  6040 
       
  6041     return {
       
  6042       start: function( event, options ) {
       
  6043 
       
  6044         var interval = function() {
       
  6045 
       
  6046           if( !window.Mustache ) {
       
  6047             setTimeout( function() {
       
  6048               interval();
       
  6049             }, 10 );
       
  6050           } else {
       
  6051 
       
  6052             // if dynamic, freshen json data on every call to start, just in case.
       
  6053             if ( getData ) {
       
  6054               data = getData( options );
       
  6055             }
       
  6056 
       
  6057             if ( getTemplate ) {
       
  6058               template = getTemplate( options );
       
  6059             }
       
  6060 
       
  6061             var html = Mustache.to_html( template,
       
  6062                                          data
       
  6063                                        ).replace( /^\s*/mg, "" );
       
  6064             options.container.innerHTML = html;
       
  6065           }
       
  6066         };
       
  6067 
       
  6068         interval();
       
  6069 
       
  6070       },
       
  6071 
       
  6072       end: function( event, options ) {
       
  6073         options.container.innerHTML = "";
       
  6074       },
       
  6075       _teardown: function( options ) {
       
  6076         getData = data = getTemplate = template = null;
       
  6077       }
       
  6078     };
       
  6079   },
       
  6080   {
       
  6081     about: {
       
  6082       name: "Popcorn Mustache Plugin",
       
  6083       version: "0.1",
       
  6084       author: "David Humphrey (@humphd)",
       
  6085       website: "http://vocamus.net/dave"
       
  6086     },
       
  6087     options: {
       
  6088       start: {
       
  6089         elem: "input",
       
  6090         type: "number",
       
  6091         label: "Start"
       
  6092       },
       
  6093       end: {
       
  6094         elem: "input",
       
  6095         type: "number",
       
  6096         label: "End"
       
  6097       },
       
  6098       target: "mustache-container",
       
  6099       template: {
       
  6100         elem: "input",
       
  6101         type: "text",
       
  6102         label: "Template"
       
  6103       },
       
  6104       data: {
       
  6105         elem: "input",
       
  6106         type: "text",
       
  6107         label: "Data"
       
  6108       },
       
  6109       dynamic: {
       
  6110         elem: "input",
       
  6111         type: "checkbox",
       
  6112         label: "Dynamic",
       
  6113         "default": true
       
  6114       }
       
  6115     }
       
  6116   });
       
  6117 })( Popcorn );
       
  6118 // PLUGIN: OPENMAP
       
  6119 ( function ( Popcorn ) {
       
  6120 
       
  6121   /**
       
  6122    * openmap popcorn plug-in
       
  6123    * Adds an OpenLayers map and open map tiles (OpenStreetMap [default], NASA WorldWind, or USGS Topographic)
       
  6124    * Based on the googlemap popcorn plug-in. No StreetView support
       
  6125    * Options parameter will need a start, end, target, type, zoom, lat and lng
       
  6126    * -Start is the time that you want this plug-in to execute
       
  6127    * -End is the time that you want this plug-in to stop executing
       
  6128    * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
       
  6129    * -Type [optional] either: ROADMAP (OpenStreetMap), SATELLITE (NASA WorldWind / LandSat), or TERRAIN (USGS).
       
  6130    *                          The Stamen custom map types can also be used (http://maps.stamen.com): STAMEN-TONER,
       
  6131    *                          STAMEN-TERRAIN, or STAMEN-WATERCOLOR.
       
  6132    * -Zoom [optional] defaults to 2
       
  6133    * -Lat and Lng are the coordinates of the map if location is not named
       
  6134    * -Location is a name of a place to center the map, geocoded to coordinates using TinyGeocoder.com
       
  6135    * -Markers [optional] is an array of map marker objects, with the following properties:
       
  6136    * --Icon is the URL of a map marker image
       
  6137    * --Size [optional] is the radius in pixels of the scaled marker image (default is 14)
       
  6138    * --Text [optional] is the HTML content of the map marker -- if your popcorn instance is named 'popped', use <script>popped.currentTime(10);</script> to control the video
       
  6139    * --Lat and Lng are coordinates of the map marker if location is not specified
       
  6140    * --Location is a name of a place for the map marker, geocoded to coordinates using TinyGeocoder.com
       
  6141    *  Note: using location requires extra loading time, also not specifying both lat/lng and location will
       
  6142    * cause a JavaScript error.
       
  6143    * @param {Object} options
       
  6144    *
       
  6145    * Example:
       
  6146      var p = Popcorn( '#video' )
       
  6147         .openmap({
       
  6148           start: 5,
       
  6149           end: 15,
       
  6150           type: 'ROADMAP',
       
  6151           target: 'map',
       
  6152           lat: 43.665429,
       
  6153           lng: -79.403323
       
  6154         })
       
  6155    *
       
  6156    */
       
  6157   var newdiv,
       
  6158       i = 1;
       
  6159 
       
  6160   function toggle( container, display ) {
       
  6161     if ( container.map ) {
       
  6162       container.map.div.style.display = display;
       
  6163       return;
       
  6164     }
       
  6165 
       
  6166     setTimeout(function() {
       
  6167       toggle( container, display );
       
  6168     }, 10 );
       
  6169   }
       
  6170 
       
  6171   Popcorn.plugin( "openmap", function( options ){
       
  6172     var newdiv,
       
  6173         centerlonlat,
       
  6174         projection,
       
  6175         displayProjection,
       
  6176         pointLayer,
       
  6177         selectControl,
       
  6178         popup,
       
  6179         isGeoReady,
       
  6180         target = document.getElementById( options.target );
       
  6181 
       
  6182     // create a new div within the target div
       
  6183     // this is later passed on to the maps api
       
  6184     newdiv = document.createElement( "div" );
       
  6185     newdiv.id = "openmapdiv" + i;
       
  6186     newdiv.style.width = "100%";
       
  6187     newdiv.style.height = "100%";
       
  6188     i++;
       
  6189 
       
  6190     target && target.appendChild( newdiv );
       
  6191 
       
  6192     // callback function fires when the script is run
       
  6193     isGeoReady = function() {
       
  6194       if ( ! ( window.OpenLayers && window.OpenLayers.Layer.Stamen ) ) {
       
  6195         setTimeout(function() {
       
  6196           isGeoReady();
       
  6197         }, 50);
       
  6198       } else {
       
  6199         if ( options.location ) {
       
  6200           // set a dummy center at start
       
  6201           location = new OpenLayers.LonLat( 0, 0 );
       
  6202           // query TinyGeocoder and re-center in callback
       
  6203           Popcorn.getJSONP(
       
  6204             "//tinygeocoder.com/create-api.php?q=" + options.location + "&callback=jsonp",
       
  6205             function( latlng ) {
       
  6206               centerlonlat = new OpenLayers.LonLat( latlng[ 1 ], latlng[ 0 ] );
       
  6207             }
       
  6208           );
       
  6209         } else {
       
  6210           centerlonlat = new OpenLayers.LonLat( options.lng, options.lat );
       
  6211         }
       
  6212 
       
  6213         options.type = options.type || "ROADMAP";
       
  6214         switch( options.type ) {
       
  6215           case "SATELLITE" :
       
  6216             // add NASA WorldWind / LANDSAT map
       
  6217             options.map = new OpenLayers.Map({
       
  6218               div: newdiv,
       
  6219               maxResolution: 0.28125,
       
  6220               tileSize: new OpenLayers.Size( 512, 512 )
       
  6221             });
       
  6222             var worldwind = new OpenLayers.Layer.WorldWind(
       
  6223               "LANDSAT",
       
  6224               "//worldwind25.arc.nasa.gov/tile/tile.aspx",
       
  6225               2.25, 4,
       
  6226               { T: "105" }
       
  6227             );
       
  6228             options.map.addLayer( worldwind );
       
  6229             displayProjection = new OpenLayers.Projection( "EPSG:4326" );
       
  6230             projection = new OpenLayers.Projection( "EPSG:4326" );
       
  6231             break;
       
  6232           case "TERRAIN":
       
  6233             // add terrain map ( USGS )
       
  6234             displayProjection = new OpenLayers.Projection( "EPSG:4326" );
       
  6235             projection = new OpenLayers.Projection( "EPSG:4326" );
       
  6236             options.map = new OpenLayers.Map({
       
  6237               div: newdiv,
       
  6238               projection: projection
       
  6239             });
       
  6240             var relief = new OpenLayers.Layer.WMS(
       
  6241               "USGS Terraserver",
       
  6242               "//terraserver-usa.org/ogcmap.ashx?",
       
  6243               { layers: "DRG" }
       
  6244             );
       
  6245             options.map.addLayer( relief );
       
  6246             break;
       
  6247           case "STAMEN-TONER":
       
  6248           case "STAMEN-WATERCOLOR":
       
  6249           case "STAMEN-TERRAIN":
       
  6250             var layerName = options.type.replace("STAMEN-", "").toLowerCase();
       
  6251             var sLayer = new OpenLayers.Layer.Stamen( layerName );
       
  6252             displayProjection = new OpenLayers.Projection( "EPSG:4326" );
       
  6253             projection = new OpenLayers.Projection( 'EPSG:900913' );
       
  6254             centerlonlat = centerlonlat.transform( displayProjection, projection );
       
  6255             options.map = new OpenLayers.Map( {
       
  6256               div: newdiv,
       
  6257               projection: projection,
       
  6258               displayProjection: displayProjection,
       
  6259               controls: [
       
  6260                 new OpenLayers.Control.Navigation(),
       
  6261                 new OpenLayers.Control.PanPanel(),
       
  6262                 new OpenLayers.Control.ZoomPanel()
       
  6263               ]
       
  6264             } );
       
  6265             options.map.addLayer( sLayer );
       
  6266             break;
       
  6267           default: /* case "ROADMAP": */
       
  6268             // add OpenStreetMap layer
       
  6269             projection = new OpenLayers.Projection( 'EPSG:900913' );
       
  6270             displayProjection = new OpenLayers.Projection( 'EPSG:4326' );
       
  6271             centerlonlat = centerlonlat.transform( displayProjection, projection );
       
  6272             options.map = new OpenLayers.Map({
       
  6273               div: newdiv,
       
  6274               projection: projection,
       
  6275               "displayProjection": displayProjection
       
  6276             });
       
  6277             var osm = new OpenLayers.Layer.OSM();
       
  6278             options.map.addLayer( osm );
       
  6279             break;
       
  6280         }
       
  6281 
       
  6282         if ( options.map ) {
       
  6283           options.map.setCenter(centerlonlat, options.zoom || 10);
       
  6284           options.map.div.style.display = "none";
       
  6285         }
       
  6286       }
       
  6287     };
       
  6288 
       
  6289     isGeoReady();
       
  6290 
       
  6291     return {
       
  6292 
       
  6293       /**
       
  6294        * @member openmap
       
  6295        * The setup function will be executed when the plug-in is instantiated
       
  6296        */
       
  6297       _setup: function( options ) {
       
  6298 
       
  6299         // insert openlayers api script once
       
  6300         if ( !window.OpenLayers ) {
       
  6301           Popcorn.getScript( "//openlayers.org/api/OpenLayers.js", function() {
       
  6302             Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js" );
       
  6303           } );
       
  6304         }
       
  6305 
       
  6306         var isReady = function() {
       
  6307           // wait until OpenLayers has been loaded, and the start function is run, before adding map
       
  6308           if ( !options.map ) {
       
  6309             setTimeout(function() {
       
  6310               isReady();
       
  6311             }, 13 );
       
  6312           } else {
       
  6313 
       
  6314             // default zoom is 2
       
  6315             options.zoom = options.zoom || 2;
       
  6316 
       
  6317             // make sure options.zoom is a number
       
  6318             if ( options.zoom && typeof options.zoom !== "number" ) {
       
  6319               options.zoom = +options.zoom;
       
  6320             }
       
  6321 
       
  6322             // reset the location and zoom just in case the user played with the map
       
  6323             options.map.setCenter( centerlonlat, options.zoom );
       
  6324             if ( options.markers ) {
       
  6325               var layerStyle = OpenLayers.Util.extend( {} , OpenLayers.Feature.Vector.style[ "default" ] ),
       
  6326                   featureSelected = function( clickInfo ) {
       
  6327                     clickedFeature = clickInfo.feature;
       
  6328                     if ( !clickedFeature.attributes.text ) {
       
  6329                       return;
       
  6330                     }
       
  6331                     popup = new OpenLayers.Popup.FramedCloud(
       
  6332                       "featurePopup",
       
  6333                       clickedFeature.geometry.getBounds().getCenterLonLat(),
       
  6334                       new OpenLayers.Size( 120, 250 ),
       
  6335                       clickedFeature.attributes.text,
       
  6336                       null,
       
  6337                       true,
       
  6338                       function( closeInfo ) {
       
  6339                         selectControl.unselect( this.feature );
       
  6340                       }
       
  6341                     );
       
  6342                     clickedFeature.popup = popup;
       
  6343                     popup.feature = clickedFeature;
       
  6344                     options.map.addPopup( popup );
       
  6345                   },
       
  6346                   featureUnSelected = function( clickInfo ) {
       
  6347                     feature = clickInfo.feature;
       
  6348                     if ( feature.popup ) {
       
  6349                       popup.feature = null;
       
  6350                       options.map.removePopup( feature.popup );
       
  6351                       feature.popup.destroy();
       
  6352                       feature.popup = null;
       
  6353                     }
       
  6354                   },
       
  6355                   gcThenPlotMarker = function( myMarker ) {
       
  6356                     Popcorn.getJSONP(
       
  6357                       "//tinygeocoder.com/create-api.php?q=" + myMarker.location + "&callback=jsonp",
       
  6358                       function( latlng ) {
       
  6359                         var myPoint = new OpenLayers.Geometry.Point( latlng[1], latlng[0] ).transform( displayProjection, projection ),
       
  6360                             myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
       
  6361                         if ( !myMarker.size || isNaN( myMarker.size ) ) {
       
  6362                           myMarker.size = 14;
       
  6363                         }
       
  6364                         myPointStyle.pointRadius = myMarker.size;
       
  6365                         myPointStyle.graphicOpacity = 1;
       
  6366                         myPointStyle.externalGraphic = myMarker.icon;
       
  6367                         var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
       
  6368                         if ( myMarker.text ) {
       
  6369                           myPointFeature.attributes = {
       
  6370                             text: myMarker.text
       
  6371                           };
       
  6372                         }
       
  6373                         pointLayer.addFeatures( [ myPointFeature ] );
       
  6374                       }
       
  6375                     );
       
  6376                   };
       
  6377               pointLayer = new OpenLayers.Layer.Vector( "Point Layer", { style: layerStyle } );
       
  6378               options.map.addLayer( pointLayer );
       
  6379               for ( var m = 0, l = options.markers.length; m < l ; m++ ) {
       
  6380                 var myMarker = options.markers[ m ];
       
  6381                 if( myMarker.text ){
       
  6382                   if( !selectControl ){
       
  6383                     selectControl = new OpenLayers.Control.SelectFeature( pointLayer );
       
  6384                     options.map.addControl( selectControl );
       
  6385                     selectControl.activate();
       
  6386                     pointLayer.events.on({
       
  6387                       "featureselected": featureSelected,
       
  6388                       "featureunselected": featureUnSelected
       
  6389                     });
       
  6390                   }
       
  6391                 }
       
  6392                 if ( myMarker.location ) {
       
  6393                   var geocodeThenPlotMarker = gcThenPlotMarker;
       
  6394                   geocodeThenPlotMarker( myMarker );
       
  6395                 } else {
       
  6396                   var myPoint = new OpenLayers.Geometry.Point( myMarker.lng, myMarker.lat ).transform( displayProjection, projection ),
       
  6397                       myPointStyle = OpenLayers.Util.extend( {}, layerStyle );
       
  6398                   if ( !myMarker.size || isNaN( myMarker.size ) ) {
       
  6399                     myMarker.size = 14;
       
  6400                   }
       
  6401                   myPointStyle.pointRadius = myMarker.size;
       
  6402                   myPointStyle.graphicOpacity = 1;
       
  6403                   myPointStyle.externalGraphic = myMarker.icon;
       
  6404                   var myPointFeature = new OpenLayers.Feature.Vector( myPoint, null, myPointStyle );
       
  6405                   if ( myMarker.text ) {
       
  6406                     myPointFeature.attributes = {
       
  6407                       text: myMarker.text
       
  6408                     };
       
  6409                   }
       
  6410                   pointLayer.addFeatures( [ myPointFeature ] );
       
  6411                 }
       
  6412               }
       
  6413             }
       
  6414           }
       
  6415         };
       
  6416 
       
  6417         isReady();
       
  6418       },
       
  6419 
       
  6420       /**
       
  6421        * @member openmap
       
  6422        * The start function will be executed when the currentTime
       
  6423        * of the video  reaches the start time provided by the
       
  6424        * options variable
       
  6425        */
       
  6426       start: function( event, options ) {
       
  6427         toggle( options, "block" );
       
  6428       },
       
  6429 
       
  6430       /**
       
  6431        * @member openmap
       
  6432        * The end function will be executed when the currentTime
       
  6433        * of the video reaches the end time provided by the
       
  6434        * options variable
       
  6435        */
       
  6436       end: function( event, options ) {
       
  6437           toggle( options, "none" );
       
  6438       },
       
  6439 
       
  6440       _teardown: function( options ) {
       
  6441 
       
  6442         target && target.removeChild( newdiv );
       
  6443         newdiv = map = centerlonlat = projection = displayProjection = pointLayer = selectControl = popup = null;
       
  6444       }
       
  6445     };
       
  6446   },
       
  6447   {
       
  6448     about:{
       
  6449       name: "Popcorn OpenMap Plugin",
       
  6450       version: "0.3",
       
  6451       author: "@mapmeld",
       
  6452       website: "mapadelsur.blogspot.com"
       
  6453     },
       
  6454     options:{
       
  6455       start: {
       
  6456         elem: "input",
       
  6457         type: "number",
       
  6458         label: "Start"
       
  6459       },
       
  6460       end: {
       
  6461         elem: "input",
       
  6462         type: "number",
       
  6463         label: "End"
       
  6464       },
       
  6465       target: "map-container",
       
  6466       type: {
       
  6467         elem: "select",
       
  6468         options: [ "ROADMAP", "SATELLITE", "TERRAIN" ],
       
  6469         label: "Map Type",
       
  6470         optional: true
       
  6471       },
       
  6472       zoom: {
       
  6473         elem: "input",
       
  6474         type: "number",
       
  6475         label: "Zoom",
       
  6476         "default": 2
       
  6477       },
       
  6478       lat: {
       
  6479         elem: "input",
       
  6480         type: "text",
       
  6481         label: "Lat",
       
  6482         optional: true
       
  6483       },
       
  6484       lng: {
       
  6485         elem: "input",
       
  6486         type: "text",
       
  6487         label: "Lng",
       
  6488         optional: true
       
  6489       },
       
  6490       location: {
       
  6491         elem: "input",
       
  6492         type: "text",
       
  6493         label: "Location",
       
  6494         "default": "Toronto, Ontario, Canada"
       
  6495       },
       
  6496       markers: {
       
  6497         elem: "input",
       
  6498         type: "text",
       
  6499         label: "List Markers",
       
  6500         optional: true
       
  6501       }
       
  6502     }
       
  6503   });
       
  6504 }) ( Popcorn );
       
  6505 /**
       
  6506 * Pause Popcorn Plug-in
       
  6507 *
       
  6508 * When this plugin is used, links on the webpage, when clicked, will pause
       
  6509 * popcorn videos that especified 'pauseOnLinkClicked' as an option. Links may
       
  6510 * cause a new page to display on a new window, or may cause a new page to
       
  6511 * display in the current window, in which case the videos won't be available
       
  6512 * anymore. It only affects anchor tags. It does not affect objects with click
       
  6513 * events that act as anchors.
       
  6514 *
       
  6515 * Example:
       
  6516  var p = Popcorn('#video', { pauseOnLinkClicked : true } )
       
  6517    .play();
       
  6518 *
       
  6519 */
       
  6520 
       
  6521 document.addEventListener( "click", function( event ) {
       
  6522 
       
  6523   var targetElement = event.target;
       
  6524 
       
  6525   //Some browsers use an element as the target, some use the text node inside
       
  6526   if ( targetElement.nodeName === "A" || targetElement.parentNode && targetElement.parentNode.nodeName === "A" ) {
       
  6527     Popcorn.instances.forEach( function( video ) {
       
  6528       if ( video.options.pauseOnLinkClicked ) {
       
  6529         video.pause();
       
  6530       }
       
  6531     });
       
  6532   }
       
  6533 }, false );
       
  6534 // PLUGIN: Wordriver
       
  6535 
       
  6536 (function ( Popcorn ) {
       
  6537 
       
  6538   var container = {},
       
  6539       spanLocation = 0,
       
  6540       setupContainer = function( target ) {
       
  6541 
       
  6542         container[ target ] = document.createElement( "div" );
       
  6543 
       
  6544         var t = document.getElementById( target );
       
  6545         t && t.appendChild( container[ target ] );
       
  6546 
       
  6547         container[ target ].style.height = "100%";
       
  6548         container[ target ].style.position = "relative";
       
  6549 
       
  6550         return container[ target ];
       
  6551       },
       
  6552       // creates an object of supported, cross platform css transitions
       
  6553       span = document.createElement( "span" ),
       
  6554       prefixes = [ "webkit", "Moz", "ms", "O", "" ],
       
  6555       specProp = [ "Transform", "TransitionDuration", "TransitionTimingFunction" ],
       
  6556       supports = {},
       
  6557       prop;
       
  6558 
       
  6559   document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
       
  6560 
       
  6561   for ( var sIdx = 0, sLen = specProp.length; sIdx < sLen; sIdx++ ) {
       
  6562 
       
  6563     for ( var pIdx = 0, pLen = prefixes.length; pIdx < pLen; pIdx++ ) {
       
  6564 
       
  6565       prop = prefixes[ pIdx ] + specProp[ sIdx ];
       
  6566 
       
  6567       if ( prop in span.style ) {
       
  6568 
       
  6569         supports[ specProp[ sIdx ].toLowerCase() ] = prop;
       
  6570         break;
       
  6571       }
       
  6572     }
       
  6573   }
       
  6574 
       
  6575   // Garbage collect support test span
       
  6576   document.getElementsByTagName( "head" )[ 0 ].appendChild( span );
       
  6577 
       
  6578   /**
       
  6579    * Word River popcorn plug-in
       
  6580    * Displays a string of text, fading it in and out
       
  6581    * while transitioning across the height of the parent container
       
  6582    * for the duration of the instance  (duration = end - start)
       
  6583    *
       
  6584    * @param {Object} options
       
  6585    *
       
  6586    * Example:
       
  6587      var p = Popcorn( '#video' )
       
  6588         .wordriver({
       
  6589           start: 5,                      // When to begin the Word River animation
       
  6590           end: 15,                       // When to finish the Word River animation
       
  6591           text: 'Hello World',           // The text you want to be displayed by Word River
       
  6592           target: 'wordRiverDiv',        // The target div to append the text to
       
  6593           color: "blue"                  // The color of the text. (can be Hex value i.e. #FFFFFF )
       
  6594         } )
       
  6595    *
       
  6596    */
       
  6597 
       
  6598   Popcorn.plugin( "wordriver" , {
       
  6599 
       
  6600       manifest: {
       
  6601         about:{
       
  6602           name: "Popcorn WordRiver Plugin"
       
  6603         },
       
  6604         options: {
       
  6605           start: {
       
  6606             elem: "input",
       
  6607             type: "number",
       
  6608             label: "Start"
       
  6609           },
       
  6610           end: {
       
  6611             elem: "input",
       
  6612             type: "number",
       
  6613             label: "End"
       
  6614           },
       
  6615           target: "wordriver-container",
       
  6616           text: {
       
  6617             elem: "input",
       
  6618             type: "text",
       
  6619             label: "Text",
       
  6620             "default": "Popcorn.js"
       
  6621           },
       
  6622           color: {
       
  6623             elem: "input",
       
  6624             type: "text",
       
  6625             label: "Color",
       
  6626             "default": "Green",
       
  6627             optional: true
       
  6628           }
       
  6629         }
       
  6630       },
       
  6631 
       
  6632       _setup: function( options ) {
       
  6633 
       
  6634         options._duration = options.end - options.start;
       
  6635         options._container = container[ options.target ] || setupContainer( options.target );
       
  6636 
       
  6637         options.word = document.createElement( "span" );
       
  6638         options.word.style.position = "absolute";
       
  6639 
       
  6640         options.word.style.whiteSpace = "nowrap";
       
  6641         options.word.style.opacity = 0;
       
  6642 
       
  6643         options.word.style.MozTransitionProperty = "opacity, -moz-transform";
       
  6644         options.word.style.webkitTransitionProperty = "opacity, -webkit-transform";
       
  6645         options.word.style.OTransitionProperty = "opacity, -o-transform";
       
  6646         options.word.style.transitionProperty = "opacity, transform";
       
  6647 
       
  6648         options.word.style[ supports.transitionduration ] = 1 + "s, " + options._duration + "s";
       
  6649         options.word.style[ supports.transitiontimingfunction ] = "linear";
       
  6650 
       
  6651         options.word.innerHTML = options.text;
       
  6652         options.word.style.color = options.color || "black";
       
  6653       },
       
  6654       start: function( event, options ){
       
  6655 
       
  6656         options._container.appendChild( options.word );
       
  6657 
       
  6658         // Resets the transform when changing to a new currentTime before the end event occurred.
       
  6659         options.word.style[ supports.transform ] = "";
       
  6660 
       
  6661         options.word.style.fontSize = ~~( 30 + 20 * Math.random() ) + "px";
       
  6662         spanLocation = spanLocation % ( options._container.offsetWidth - options.word.offsetWidth );
       
  6663         options.word.style.left = spanLocation + "px";
       
  6664         spanLocation += options.word.offsetWidth + 10;
       
  6665         options.word.style[ supports.transform ] = "translateY(" +
       
  6666           ( options._container.offsetHeight - options.word.offsetHeight ) + "px)";
       
  6667 
       
  6668         options.word.style.opacity = 1;
       
  6669 
       
  6670         // automatically clears the word based on time
       
  6671         setTimeout( function() {
       
  6672 
       
  6673 		      options.word.style.opacity = 0;
       
  6674         // ensures at least one second exists, because the fade animation is 1 second
       
  6675 		    }, ( ( (options.end - options.start) - 1 ) || 1 ) * 1000 );
       
  6676       },
       
  6677       end: function( event, options ){
       
  6678 
       
  6679         // manually clears the word based on user interaction
       
  6680         options.word.style.opacity = 0;
       
  6681       },
       
  6682       _teardown: function( options ) {
       
  6683 
       
  6684         var target = document.getElementById( options.target );
       
  6685         // removes word span from generated container
       
  6686         options.word.parentNode && options._container.removeChild( options.word );
       
  6687 
       
  6688         // if no more word spans exist in container, remove container
       
  6689         container[ options.target ] &&
       
  6690           !container[ options.target ].childElementCount &&
       
  6691           target && target.removeChild( container[ options.target ] ) &&
       
  6692           delete container[ options.target ];
       
  6693       }
       
  6694   });
       
  6695 
       
  6696 })( Popcorn );
       
  6697 // PLUGIN: Timeline
       
  6698 (function ( Popcorn ) {
       
  6699 
       
  6700   /**
       
  6701      * timeline popcorn plug-in
       
  6702      * Adds data associated with a certain time in the video, which creates a scrolling view of each item as the video progresses
       
  6703      * Options parameter will need a start, target, title, and text
       
  6704      * -Start is the time that you want this plug-in to execute
       
  6705      * -End is the time that you want this plug-in to stop executing, tho for this plugin an end time may not be needed ( optional )
       
  6706      * -Target is the id of the DOM element that you want the timeline to appear in. This element must be in the DOM
       
  6707      * -Title is the title of the current timeline box
       
  6708      * -Text is text is simply related text that will be displayed
       
  6709      * -innerHTML gives the user the option to add things such as links, buttons and so on
       
  6710      * -direction specifies whether the timeline will grow from the top or the bottom, receives input as "UP" or "DOWN"
       
  6711      * @param {Object} options
       
  6712      *
       
  6713      * Example:
       
  6714       var p = Popcorn("#video")
       
  6715         .timeline( {
       
  6716          start: 5, // seconds
       
  6717          target: "timeline",
       
  6718          title: "Seneca",
       
  6719          text: "Welcome to seneca",
       
  6720          innerHTML: "Click this link <a href='http://www.google.ca'>Google</a>"
       
  6721       } )
       
  6722     *
       
  6723   */
       
  6724 
       
  6725   var i = 1;
       
  6726 
       
  6727   Popcorn.plugin( "timeline" , function( options ) {
       
  6728 
       
  6729     var target = document.getElementById( options.target ),
       
  6730         contentDiv = document.createElement( "div" ),
       
  6731         container,
       
  6732         goingUp = true;
       
  6733 
       
  6734     if ( target && !target.firstChild ) {
       
  6735       target.appendChild ( container = document.createElement( "div" ) );
       
  6736       container.style.width = "inherit";
       
  6737       container.style.height = "inherit";
       
  6738       container.style.overflow = "auto";
       
  6739     } else {
       
  6740       container = target.firstChild;
       
  6741     }
       
  6742 
       
  6743     contentDiv.style.display = "none";
       
  6744     contentDiv.id = "timelineDiv" + i;
       
  6745 
       
  6746     //  Default to up if options.direction is non-existant or not up or down
       
  6747     options.direction = options.direction || "up";
       
  6748     if ( options.direction.toLowerCase() === "down" ) {
       
  6749 
       
  6750       goingUp = false;
       
  6751     }
       
  6752 
       
  6753     if ( target && container ) {
       
  6754       // if this isnt the first div added to the target div
       
  6755       if( goingUp ){
       
  6756         // insert the current div before the previous div inserted
       
  6757         container.insertBefore( contentDiv, container.firstChild );
       
  6758       }
       
  6759       else {
       
  6760 
       
  6761         container.appendChild( contentDiv );
       
  6762       }
       
  6763 
       
  6764     }
       
  6765 
       
  6766     i++;
       
  6767 
       
  6768     //  Default to empty if not used
       
  6769     //options.innerHTML = options.innerHTML || "";
       
  6770 
       
  6771     contentDiv.innerHTML = "<p><span id='big' style='font-size:24px; line-height: 130%;' >" + options.title + "</span><br />" +
       
  6772     "<span id='mid' style='font-size: 16px;'>" + options.text + "</span><br />" + options.innerHTML;
       
  6773 
       
  6774     return {
       
  6775 
       
  6776       start: function( event, options ) {
       
  6777         contentDiv.style.display = "block";
       
  6778 
       
  6779         if( options.direction === "down" ) {
       
  6780           container.scrollTop = container.scrollHeight;
       
  6781         }
       
  6782       },
       
  6783 
       
  6784       end: function( event, options ) {
       
  6785         contentDiv.style.display = "none";
       
  6786       },
       
  6787 
       
  6788       _teardown: function( options ) {
       
  6789 
       
  6790         ( container && contentDiv ) && container.removeChild( contentDiv ) && !container.firstChild && target.removeChild( container );
       
  6791       }
       
  6792     };
       
  6793   },
       
  6794   {
       
  6795 
       
  6796     about: {
       
  6797       name: "Popcorn Timeline Plugin",
       
  6798       version: "0.1",
       
  6799       author: "David Seifried @dcseifried",
       
  6800       website: "dseifried.wordpress.com"
       
  6801     },
       
  6802 
       
  6803     options: {
       
  6804       start: {
       
  6805         elem: "input",
       
  6806         type: "number",
       
  6807         label: "Start"
       
  6808       },
       
  6809       end: {
       
  6810         elem: "input",
       
  6811         type: "number",
       
  6812         label: "End"
       
  6813       },
       
  6814       target: "feed-container",
       
  6815       title: {
       
  6816         elem: "input",
       
  6817         type: "text",
       
  6818         label: "Title"
       
  6819       },
       
  6820       text: {
       
  6821         elem: "input",
       
  6822         type: "text",
       
  6823         label: "Text"
       
  6824       },
       
  6825       innerHTML: {
       
  6826         elem: "input",
       
  6827         type: "text",
       
  6828         label: "HTML Code",
       
  6829         optional: true
       
  6830       },
       
  6831       direction: {
       
  6832         elem: "select",
       
  6833         options: [ "DOWN", "UP" ],
       
  6834         label: "Direction",
       
  6835         optional: true
       
  6836       }
       
  6837     }
       
  6838   });
       
  6839 
       
  6840 })( Popcorn );
       
  6841 // PLUGIN: documentcloud
       
  6842 
       
  6843 (function( Popcorn, document ) {
       
  6844 
       
  6845   /**
       
  6846    * Document Cloud popcorn plug-in
       
  6847    *
       
  6848    * @param {Object} options
       
  6849    *
       
  6850    * Example:
       
  6851    *  var p = Popcorn("#video")
       
  6852    *     // Let the pdf plugin load your PDF file for you using pdfUrl.
       
  6853    *     .documentcloud({
       
  6854    *       start: 45
       
  6855    *       url: "http://www.documentcloud.org/documents/70050-urbina-day-1-in-progress.html", // or .js
       
  6856    *       width: ...,
       
  6857    *       height: ...,
       
  6858    *       zoom: ...,
       
  6859    *       page: ...,
       
  6860    *       container: ...
       
  6861    *     });
       
  6862 
       
  6863 api - https://github.com/documentcloud/document-viewer/blob/master/public/javascripts/DV/controllers/api.js
       
  6864 
       
  6865    */
       
  6866 
       
  6867    // track registered plugins by document
       
  6868    var documentRegistry = {};
       
  6869 
       
  6870   Popcorn.plugin( "documentcloud", {
       
  6871 
       
  6872     manifest: {
       
  6873       about: {
       
  6874         name: "Popcorn Document Cloud Plugin",
       
  6875         version: "0.1",
       
  6876         author: "@humphd, @ChrisDeCairos",
       
  6877         website: "http://vocamus.net/dave"
       
  6878       },
       
  6879       options: {
       
  6880         start: {
       
  6881           elem: "input",
       
  6882           type: "number",
       
  6883           label: "Start"
       
  6884         },
       
  6885         end: {
       
  6886           elem: "input",
       
  6887           type: "number",
       
  6888           label: "End"
       
  6889         },
       
  6890         target: "documentcloud-container",
       
  6891         width: {
       
  6892           elem: "input",
       
  6893           type: "text",
       
  6894           label: "Width",
       
  6895           optional: true
       
  6896         },
       
  6897         height: {
       
  6898           elem: "input",
       
  6899           type: "text",
       
  6900           label: "Height",
       
  6901           optional: true
       
  6902         },
       
  6903         src: {
       
  6904           elem: "input",
       
  6905           type: "url",
       
  6906           label: "PDF URL",
       
  6907           "default": "http://www.documentcloud.org/documents/70050-urbina-day-1-in-progress.html"
       
  6908         },
       
  6909         preload: {
       
  6910           elem: "input",
       
  6911           type: "checkbox",
       
  6912           label: "Preload",
       
  6913           "default": true
       
  6914         },
       
  6915         page: {
       
  6916           elem: "input",
       
  6917           type: "number",
       
  6918           label: "Page Number",
       
  6919           optional: true
       
  6920         },
       
  6921         aid: {
       
  6922           elem: "input",
       
  6923           type: "number",
       
  6924           label: "Annotation Id",
       
  6925           optional: true
       
  6926         }
       
  6927       }
       
  6928     },
       
  6929 
       
  6930     _setup: function( options ) {
       
  6931       var DV = window.DV = window.DV || {},
       
  6932           that = this;
       
  6933 
       
  6934       //setup elem...
       
  6935       function load() {
       
  6936         DV.loaded = false;
       
  6937         // swap .html URL to .js for API call
       
  6938         var url = options.url.replace( /\.html$/, ".js" ),
       
  6939           target = options.target,
       
  6940           targetDiv = document.getElementById( target ),
       
  6941           containerDiv = document.createElement( "div" ),
       
  6942           containerDivSize = Popcorn.position( targetDiv ),
       
  6943           // need to use size of div if not given
       
  6944           width = options.width || containerDivSize.width,
       
  6945           height = options.height || containerDivSize.height,
       
  6946           sidebar = options.sidebar || true,
       
  6947           text = options.text || true,
       
  6948           pdf = options.pdf || true,
       
  6949           showAnnotations = options.showAnnotations || true,
       
  6950           zoom = options.zoom || 700,
       
  6951           search = options.search || true,
       
  6952           page = options.page,
       
  6953           container;
       
  6954 
       
  6955         function setOptions( viewer ) {
       
  6956           options._key = viewer.api.getId();
       
  6957 
       
  6958           options._changeView = function ( viewer ) {
       
  6959             if ( options.aid ) {
       
  6960               viewer.pageSet.showAnnotation( viewer.api.getAnnotation( options.aid ) );
       
  6961             } else {
       
  6962               viewer.api.setCurrentPage( options.page );
       
  6963             }
       
  6964           };
       
  6965         }
       
  6966 
       
  6967         function documentIsLoaded( url ) {
       
  6968           var found = false;
       
  6969           Popcorn.forEach( DV.viewers, function( viewer, idx ) {
       
  6970             if( viewer.api.getSchema().canonicalURL === url ) {
       
  6971               var targetDoc;
       
  6972               setOptions( viewer );
       
  6973               targetDoc = documentRegistry[ options._key ];
       
  6974               options._containerId = targetDoc.id;
       
  6975               targetDoc.num += 1;
       
  6976               found = true;
       
  6977               DV.loaded = true;
       
  6978             }
       
  6979           });
       
  6980           return found;
       
  6981         }
       
  6982 
       
  6983         function createRegistryEntry() {
       
  6984           var entry = {
       
  6985             num: 1,
       
  6986             id: options._containerId
       
  6987           };
       
  6988           documentRegistry[ options._key ] = entry;
       
  6989           DV.loaded = true;
       
  6990         }
       
  6991 
       
  6992         if ( !documentIsLoaded( options.url ) ) {
       
  6993 
       
  6994           containerDiv.id = options._containerId = Popcorn.guid( target );
       
  6995           container = "#" + containerDiv.id;
       
  6996           targetDiv.appendChild( containerDiv );
       
  6997           that.trigger( "documentready" );
       
  6998 
       
  6999           // Figure out if we need a callback to change the page #
       
  7000           var afterLoad = options.page || options.aid ?
       
  7001             function( viewer ) {
       
  7002               setOptions( viewer );
       
  7003               options._changeView( viewer );
       
  7004               containerDiv.style.visibility = "hidden";
       
  7005               viewer.elements.pages.hide();
       
  7006               createRegistryEntry();
       
  7007             } :
       
  7008             function( viewer ) {
       
  7009               setOptions( viewer );
       
  7010               createRegistryEntry();
       
  7011               containerDiv.style.visibility = "hidden";
       
  7012               viewer.elements.pages.hide();
       
  7013             };
       
  7014           DV.load( url, {
       
  7015             width: width,
       
  7016             height: height,
       
  7017             sidebar: sidebar,
       
  7018             text: text,
       
  7019             pdf: pdf,
       
  7020             showAnnotations: showAnnotations,
       
  7021             zoom: zoom,
       
  7022             search: search,
       
  7023             container: container,
       
  7024             afterLoad: afterLoad
       
  7025           });
       
  7026         }
       
  7027       }
       
  7028       function readyCheck() {
       
  7029         if( window.DV.loaded ) {
       
  7030           load();
       
  7031         } else {
       
  7032           setTimeout( readyCheck, 25 );
       
  7033         }
       
  7034       }
       
  7035 
       
  7036       // If the viewer is already loaded, don't repeat the process.
       
  7037       if ( !DV.loading ) {
       
  7038         DV.loading = true;
       
  7039         DV.recordHit = "//www.documentcloud.org/pixel.gif";
       
  7040 
       
  7041         var link = document.createElement( "link" ),
       
  7042             head = document.getElementsByTagName( "head" )[ 0 ];
       
  7043 
       
  7044         link.rel = "stylesheet";
       
  7045         link.type = "text/css";
       
  7046         link.media = "screen";
       
  7047         link.href = "//s3.documentcloud.org/viewer/viewer-datauri.css";
       
  7048 
       
  7049         head.appendChild( link );
       
  7050 
       
  7051         // Record the fact that the viewer is loaded.
       
  7052         DV.loaded = false;
       
  7053 
       
  7054         // Request the viewer JavaScript.
       
  7055         Popcorn.getScript( "http://s3.documentcloud.org/viewer/viewer.js", function() {
       
  7056           DV.loading = false;
       
  7057           load();
       
  7058         });
       
  7059       } else {
       
  7060 
       
  7061         readyCheck();
       
  7062       }
       
  7063 
       
  7064     },
       
  7065 
       
  7066     start: function( event, options ) {
       
  7067       var elem = document.getElementById( options._containerId ),
       
  7068           viewer = DV.viewers[ options._key ];
       
  7069       ( options.page || options.aid ) && viewer &&
       
  7070         options._changeView( viewer );
       
  7071 
       
  7072       if ( elem && viewer) {
       
  7073         elem.style.visibility = "visible";
       
  7074         viewer.elements.pages.show();
       
  7075       }
       
  7076     },
       
  7077 
       
  7078     end: function( event, options ) {
       
  7079       var elem = document.getElementById( options._containerId );
       
  7080 
       
  7081       if ( elem && DV.viewers[ options._key ] ) {
       
  7082         elem.style.visibility = "hidden";
       
  7083         DV.viewers[ options._key ].elements.pages.hide();
       
  7084       }
       
  7085     },
       
  7086 
       
  7087     _teardown: function( options ) {
       
  7088       var elem = document.getElementById( options._containerId ),
       
  7089           key = options._key;
       
  7090       if ( key && DV.viewers[ key ] && --documentRegistry[ key ].num === 0 ) {
       
  7091         DV.viewers[ key ].api.unload();
       
  7092 
       
  7093         while ( elem.hasChildNodes() ) {
       
  7094           elem.removeChild( elem.lastChild );
       
  7095         }
       
  7096         elem.parentNode.removeChild( elem );
       
  7097       }
       
  7098     }
       
  7099   });
       
  7100 })( Popcorn, window.document );
       
  7101 // PARSER: 0.3 JSON
       
  7102 
       
  7103 (function (Popcorn) {
       
  7104   Popcorn.parser( "parseJSON", "JSON", function( data ) {
       
  7105 
       
  7106     // declare needed variables
       
  7107     var retObj = {
       
  7108           title: "",
       
  7109           remote: "",
       
  7110           data: []
       
  7111         },
       
  7112         manifestData = {}, 
       
  7113         dataObj = data;
       
  7114     
       
  7115     
       
  7116     /*
       
  7117       TODO: add support for filling in source children of the video element
       
  7118       
       
  7119       
       
  7120       remote: [
       
  7121         { 
       
  7122           src: "whatever.mp4", 
       
  7123           type: 'video/mp4; codecs="avc1, mp4a"'
       
  7124         }, 
       
  7125         { 
       
  7126           src: "whatever.ogv", 
       
  7127           type: 'video/ogg; codecs="theora, vorbis"'
       
  7128         }
       
  7129       ]
       
  7130 
       
  7131     */
       
  7132     
       
  7133         
       
  7134     Popcorn.forEach( dataObj.data, function ( obj, key ) {
       
  7135       retObj.data.push( obj );
       
  7136     });
       
  7137 
       
  7138     return retObj;
       
  7139   });
       
  7140 
       
  7141 })( Popcorn );
       
  7142 // PARSER: 0.1 SBV
       
  7143 
       
  7144 (function (Popcorn) {
       
  7145 
       
  7146   /**
       
  7147    * SBV popcorn parser plug-in 
       
  7148    * Parses subtitle files in the SBV format.
       
  7149    * Times are expected in H:MM:SS.MIL format, with hours optional
       
  7150    * Subtitles which don't match expected format are ignored
       
  7151    * Data parameter is given by Popcorn, will need a text.
       
  7152    * Text is the file contents to be parsed
       
  7153    * 
       
  7154    * @param {Object} data
       
  7155    * 
       
  7156    * Example:
       
  7157     0:00:02.400,0:00:07.200
       
  7158     Senator, we're making our final approach into Coruscant.
       
  7159    */
       
  7160   Popcorn.parser( "parseSBV", function( data ) {
       
  7161   
       
  7162     // declare needed variables
       
  7163     var retObj = {
       
  7164           title: "",
       
  7165           remote: "",
       
  7166           data: []
       
  7167         },
       
  7168         subs = [],
       
  7169         lines,
       
  7170         i = 0,
       
  7171         len = 0,
       
  7172         idx = 0;
       
  7173     
       
  7174     // [H:]MM:SS.MIL string to SS.MIL
       
  7175     // Will thrown exception on bad time format
       
  7176     var toSeconds = function( t_in ) {
       
  7177       var t = t_in.split( ":" ),
       
  7178           l = t.length-1,
       
  7179           time;
       
  7180       
       
  7181       try {
       
  7182         time = parseInt( t[l-1], 10 )*60 + parseFloat( t[l], 10 );
       
  7183         
       
  7184         // Hours optionally given
       
  7185         if ( l === 2 ) { 
       
  7186           time += parseInt( t[0], 10 )*3600;
       
  7187         }
       
  7188       } catch ( e ) {
       
  7189         throw "Bad cue";
       
  7190       }
       
  7191       
       
  7192       return time;
       
  7193     };
       
  7194     
       
  7195     var createTrack = function( name, attributes ) {
       
  7196       var track = {};
       
  7197       track[name] = attributes;
       
  7198       return track;
       
  7199     };
       
  7200   
       
  7201     // Here is where the magic happens
       
  7202     // Split on line breaks
       
  7203     lines = data.text.split( /(?:\r\n|\r|\n)/gm );
       
  7204     len = lines.length;
       
  7205     
       
  7206     while ( i < len ) {
       
  7207       var sub = {},
       
  7208           text = [],
       
  7209           time = lines[i++].split( "," );
       
  7210       
       
  7211       try {
       
  7212         sub.start = toSeconds( time[0] );
       
  7213         sub.end = toSeconds( time[1] );
       
  7214         
       
  7215         // Gather all lines of text
       
  7216         while ( i < len && lines[i] ) {
       
  7217           text.push( lines[i++] );
       
  7218         }
       
  7219         
       
  7220         // Join line breaks in text
       
  7221         sub.text = text.join( "<br />" );
       
  7222         subs.push( createTrack( "subtitle", sub ) );
       
  7223       } catch ( e ) {
       
  7224         // Bad cue, advance to end of cue
       
  7225         while ( i < len && lines[i] ) {
       
  7226           i++;
       
  7227         }
       
  7228       }
       
  7229       
       
  7230       // Consume empty whitespace
       
  7231       while ( i < len && !lines[i] ) {
       
  7232         i++;
       
  7233       }
       
  7234     }
       
  7235     
       
  7236     retObj.data = subs;
       
  7237 
       
  7238     return retObj;
       
  7239   });
       
  7240 
       
  7241 })( Popcorn );
       
  7242 // PARSER: 0.3 SRT
       
  7243 (function (Popcorn) {
       
  7244   /**
       
  7245    * SRT popcorn parser plug-in 
       
  7246    * Parses subtitle files in the SRT format.
       
  7247    * Times are expected in HH:MM:SS,MIL format, though HH:MM:SS.MIL also supported
       
  7248    * Ignore styling, which may occur after the end time or in-text
       
  7249    * While not part of the "official" spec, majority of players support HTML and SSA styling tags
       
  7250    * SSA-style tags are stripped, HTML style tags are left for the browser to handle:
       
  7251    *    HTML: <font>, <b>, <i>, <u>, <s>
       
  7252    *    SSA:  \N or \n, {\cmdArg1}, {\cmd(arg1, arg2, ...)}
       
  7253    
       
  7254    * Data parameter is given by Popcorn, will need a text.
       
  7255    * Text is the file contents to be parsed
       
  7256    * 
       
  7257    * @param {Object} data
       
  7258    * 
       
  7259    * Example:
       
  7260     1
       
  7261     00:00:25,712 --> 00:00:30.399
       
  7262     This text is <font color="red">RED</font> and has not been {\pos(142,120)} positioned.
       
  7263     This takes \Nup three \nentire lines.
       
  7264     This contains nested <b>bold, <i>italic, <u>underline</u> and <s>strike-through</s></u></i></b> HTML tags
       
  7265     Unclosed but <b>supported tags are left in
       
  7266     <ggg>Unsupported</ggg> HTML tags are left in, even if <hhh>not closed.
       
  7267     SSA tags with {\i1} would open and close italicize {\i0}, but are stripped
       
  7268     Multiple {\pos(142,120)\b1}SSA tags are stripped
       
  7269    */
       
  7270   Popcorn.parser( "parseSRT", function( data ) {
       
  7271 
       
  7272     // declare needed variables
       
  7273     var retObj = {
       
  7274           title: "",
       
  7275           remote: "",
       
  7276           data: []
       
  7277         },
       
  7278         subs = [],
       
  7279         i = 0,
       
  7280         idx = 0,
       
  7281         lines,
       
  7282         time,
       
  7283         text,
       
  7284         endIdx,
       
  7285         sub;
       
  7286 
       
  7287     // Here is where the magic happens
       
  7288     // Split on line breaks
       
  7289     lines = data.text.split( /(?:\r\n|\r|\n)/gm );
       
  7290     endIdx = lastNonEmptyLine( lines ) + 1;
       
  7291 
       
  7292     for( i=0; i < endIdx; i++ ) {
       
  7293       sub = {};
       
  7294       text = [];
       
  7295 
       
  7296       sub.id = parseInt( lines[i++], 10 );
       
  7297 
       
  7298       // Split on '-->' delimiter, trimming spaces as well
       
  7299       time = lines[i++].split( /[\t ]*-->[\t ]*/ );
       
  7300 
       
  7301       sub.start = toSeconds( time[0] );
       
  7302 
       
  7303       // So as to trim positioning information from end
       
  7304       idx = time[1].indexOf( " " );
       
  7305       if ( idx !== -1) {
       
  7306         time[1] = time[1].substr( 0, idx );
       
  7307       }
       
  7308       sub.end = toSeconds( time[1] );
       
  7309 
       
  7310       // Build single line of text from multi-line subtitle in file
       
  7311       while ( i < endIdx && lines[i] ) {
       
  7312         text.push( lines[i++] );
       
  7313       }
       
  7314 
       
  7315       // Join into 1 line, SSA-style linebreaks
       
  7316       // Strip out other SSA-style tags
       
  7317       sub.text = text.join( "\\N" ).replace( /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi, "" );
       
  7318       
       
  7319       // Escape HTML entities
       
  7320       sub.text = sub.text.replace( /</g, "&lt;" ).replace( />/g, "&gt;" );
       
  7321 
       
  7322       // Unescape great than and less than when it makes a valid html tag of a supported style (font, b, u, s, i)
       
  7323       // Modified version of regex from Phil Haack's blog: http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
       
  7324       // Later modified by kev: http://kevin.deldycke.com/2007/03/ultimate-regular-expression-for-html-tag-parsing-with-php/
       
  7325       sub.text = sub.text.replace( /&lt;(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)&gt;/gi, "<$1$3$7>" );
       
  7326       sub.text = sub.text.replace( /\\N/gi, "<br />" );
       
  7327       subs.push( createTrack( "subtitle", sub ) );
       
  7328     }
       
  7329 
       
  7330     retObj.data = subs;
       
  7331     return retObj;
       
  7332   });
       
  7333 
       
  7334   function createTrack( name, attributes ) {
       
  7335     var track = {};
       
  7336     track[name] = attributes;
       
  7337     return track;
       
  7338   }
       
  7339 
       
  7340   // Simple function to convert HH:MM:SS,MMM or HH:MM:SS.MMM to SS.MMM
       
  7341   // Assume valid, returns 0 on error
       
  7342   function toSeconds( t_in ) {
       
  7343     var t = t_in.split( ':' );
       
  7344 
       
  7345     try {
       
  7346       var s = t[2].split( ',' );
       
  7347 
       
  7348       // Just in case a . is decimal seperator
       
  7349       if ( s.length === 1 ) {
       
  7350         s = t[2].split( '.' );
       
  7351       }
       
  7352 
       
  7353       return parseFloat( t[0], 10 ) * 3600 + parseFloat( t[1], 10 ) * 60 + parseFloat( s[0], 10 ) + parseFloat( s[1], 10 ) / 1000;
       
  7354     } catch ( e ) {
       
  7355       return 0;
       
  7356     }
       
  7357   }
       
  7358 
       
  7359   function lastNonEmptyLine( linesArray ) {
       
  7360     var idx = linesArray.length - 1;
       
  7361 
       
  7362     while ( idx >= 0 && !linesArray[idx] ) {
       
  7363       idx--;
       
  7364     }
       
  7365 
       
  7366     return idx;
       
  7367   }
       
  7368 })( Popcorn );
       
  7369 // PARSER: 0.3 SSA/ASS
       
  7370 
       
  7371 (function ( Popcorn ) {
       
  7372   /**
       
  7373    * SSA/ASS popcorn parser plug-in
       
  7374    * Parses subtitle files in the identical SSA and ASS formats.
       
  7375    * Style information is ignored, and may be found in these
       
  7376    * formats: (\N    \n    {\pos(400,570)}     {\kf89})
       
  7377    * Out of the [Script Info], [V4 Styles], [Events], [Pictures],
       
  7378    * and [Fonts] sections, only [Events] is processed.
       
  7379    * Data parameter is given by Popcorn, will need a text.
       
  7380    * Text is the file contents to be parsed
       
  7381    *
       
  7382    * @param {Object} data
       
  7383    *
       
  7384    * Example:
       
  7385      [Script Info]
       
  7386       Title: Testing subtitles for the SSA Format
       
  7387       [V4 Styles]
       
  7388       Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
       
  7389       Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,30,30,30,0,0
       
  7390       [Events]
       
  7391       Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
       
  7392       Dialogue: 0,0:00:02.40,0:00:07.20,Default,,0000,0000,0000,,Senator, {\kf89}we're \Nmaking our final \napproach into Coruscant.
       
  7393       Dialogue: 0,0:00:09.71,0:00:13.39,Default,,0000,0000,0000,,{\pos(400,570)}Very good, Lieutenant.
       
  7394       Dialogue: 0,0:00:15.04,0:00:18.04,Default,,0000,0000,0000,,It's \Na \ntrap!
       
  7395    *
       
  7396    */
       
  7397 
       
  7398   // Register for SSA extensions
       
  7399   Popcorn.parser( "parseSSA", function( data ) {
       
  7400     // declare needed variables
       
  7401     var retObj = {
       
  7402           title: "",
       
  7403           remote: "",
       
  7404           data: [  ]
       
  7405         },
       
  7406         rNewLineFile = /(?:\r\n|\r|\n)/gm,
       
  7407         subs = [  ],
       
  7408         lines,
       
  7409         headers,
       
  7410         i = 0,
       
  7411         len;
       
  7412 
       
  7413     // Here is where the magic happens
       
  7414     // Split on line breaks
       
  7415     lines = data.text.split( rNewLineFile );
       
  7416     len = lines.length;
       
  7417 
       
  7418     // Ignore non-textual info
       
  7419     while ( i < len && lines[ i ] !== "[Events]" ) {
       
  7420       i++;
       
  7421     }
       
  7422 
       
  7423     headers = parseFieldHeaders( lines[ ++i ] );
       
  7424 
       
  7425     while ( ++i < len && lines[ i ] && lines[ i ][ 0 ] !== "[" ) {
       
  7426       try {
       
  7427         subs.push( createTrack( "subtitle", parseSub( lines[ i ], headers ) ) );
       
  7428       } catch ( e ) {}
       
  7429     }
       
  7430 
       
  7431     retObj.data = subs;
       
  7432     return retObj;
       
  7433   });
       
  7434 
       
  7435   function parseSub( line, headers ) {
       
  7436     // Trim beginning 'Dialogue: ' and split on delim
       
  7437     var fields = line.substr( 10 ).split( "," ),
       
  7438         rAdvancedStyles = /\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,
       
  7439         rNewLineSSA = /\\N/gi,
       
  7440         sub;
       
  7441 
       
  7442     sub = {
       
  7443       start: toSeconds( fields[ headers.start ] ),
       
  7444       end: toSeconds( fields[ headers.end ] )
       
  7445     };
       
  7446 
       
  7447     // Invalid time, skip
       
  7448     if ( sub.start === -1 || sub.end === -1 ) {
       
  7449       throw "Invalid time";
       
  7450     }
       
  7451 
       
  7452     // Eliminate advanced styles and convert forced line breaks
       
  7453     sub.text = getTextFromFields( fields, headers.text ).replace( rAdvancedStyles, "" ).replace( rNewLineSSA, "<br />" );
       
  7454 
       
  7455     return sub;
       
  7456   }
       
  7457 
       
  7458   // h:mm:ss.cc (centisec) string to SS.mmm
       
  7459   // Returns -1 if invalid
       
  7460   function toSeconds( t_in ) {
       
  7461     var t = t_in.split( ":" );
       
  7462 
       
  7463     // Not all there
       
  7464     if ( t_in.length !== 10 || t.length < 3 ) {
       
  7465       return -1;
       
  7466     }
       
  7467 
       
  7468     return parseInt( t[ 0 ], 10 ) * 3600 + parseInt( t[ 1 ], 10 ) * 60 + parseFloat( t[ 2 ], 10 );
       
  7469   }
       
  7470 
       
  7471   function getTextFromFields( fields, startIdx ) {
       
  7472     var fieldLen = fields.length,
       
  7473         text = [  ],
       
  7474         i = startIdx;
       
  7475 
       
  7476     // There may be commas in the text which were split, append back together into one line
       
  7477     for( ; i < fieldLen; i++ ) {
       
  7478       text.push( fields[ i ] );
       
  7479     }
       
  7480 
       
  7481     return text.join( "," );
       
  7482   }
       
  7483 
       
  7484   function createTrack( name, attributes ) {
       
  7485     var track = {};
       
  7486     track[ name ] = attributes;
       
  7487     return track;
       
  7488   }
       
  7489 
       
  7490   function parseFieldHeaders( line ) {
       
  7491     // Trim 'Format: ' off front, split on delim
       
  7492     var fields = line.substr( 8 ).split( ", " ),
       
  7493         result = {},
       
  7494         len,
       
  7495         i;
       
  7496 
       
  7497      //Find where in Dialogue string the start, end and text info is
       
  7498     for ( i = 0, len = fields.length; i < len; i++ ) {
       
  7499       if ( fields[ i ] === "Start" ) {
       
  7500         result.start = i;
       
  7501       } else if ( fields[ i ] === "End" ) {
       
  7502         result.end = i;
       
  7503       } else if ( fields[ i ] === "Text" ) {
       
  7504         result.text = i;
       
  7505       }
       
  7506     }
       
  7507 
       
  7508     return result;
       
  7509   }
       
  7510 })( Popcorn );
       
  7511 // PARSER: 1.0 TTML
       
  7512 (function ( Popcorn ) {
       
  7513   /**
       
  7514    * TTML popcorn parser plug-in
       
  7515    * Parses subtitle files in the TTML format.
       
  7516    * Times may be absolute to the timeline or relative
       
  7517    *   Absolute times are ISO 8601 format (hh:mm:ss[.mmm])
       
  7518    *   Relative times are a fraction followed by a unit metric (d.ddu)
       
  7519    *     Relative times are relative to the time given on the parent node
       
  7520    * Styling information is ignored
       
  7521    * Data parameter is given by Popcorn, will need an xml.
       
  7522    * Xml is the file contents to be processed
       
  7523    *
       
  7524    * @param {Object} data
       
  7525    *
       
  7526    * Example:
       
  7527     <tt xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling" xmlns="http://www.w3.org/2006/04/ttaf1">
       
  7528       <body region="subtitleArea">
       
  7529         <div>
       
  7530           <p xml:id="subtitle1" begin="0.76s" end="3.45s">
       
  7531             It seems a paradox, does it not,
       
  7532           </p>
       
  7533         </div>
       
  7534       </body>
       
  7535     </tt>
       
  7536    */
       
  7537 
       
  7538   var rWhitespace = /^[\s]+|[\s]+$/gm,
       
  7539       rLineBreak = /(?:\r\n|\r|\n)/gm;
       
  7540 
       
  7541   Popcorn.parser( "parseTTML", function( data ) {
       
  7542     var returnData = {
       
  7543           title: "",
       
  7544           remote: "",
       
  7545           data: []
       
  7546         },
       
  7547         node;
       
  7548 
       
  7549     // Null checks
       
  7550     if ( !data.xml || !data.xml.documentElement ) {
       
  7551       return returnData;
       
  7552     }
       
  7553 
       
  7554     node = data.xml.documentElement.firstChild;
       
  7555 
       
  7556     if ( !node ) {
       
  7557       return returnData;
       
  7558     }
       
  7559 
       
  7560     // Find body tag
       
  7561     while ( node.nodeName !== "body" ) {
       
  7562       node = node.nextSibling;
       
  7563     }
       
  7564 
       
  7565     if ( node ) {
       
  7566       returnData.data = parseChildren( node, 0 );
       
  7567     }
       
  7568 
       
  7569     return returnData;
       
  7570   });
       
  7571 
       
  7572   // Parse the children of the given node
       
  7573   function parseChildren( node, timeOffset, region ) {
       
  7574     var currNode = node.firstChild,
       
  7575         currRegion = getNodeRegion( node, region ),
       
  7576         retVal = [],
       
  7577         newOffset;
       
  7578 
       
  7579     while ( currNode ) {
       
  7580       if ( currNode.nodeType === 1 ) {
       
  7581         if ( currNode.nodeName === "p" ) {
       
  7582           // p is a textual node, process contents as subtitle
       
  7583           retVal.push( parseNode( currNode, timeOffset, currRegion ) );
       
  7584         } else if ( currNode.nodeName === "div" ) {
       
  7585           // div is container for subtitles, recurse
       
  7586           newOffset = toSeconds( currNode.getAttribute( "begin" ) );
       
  7587 
       
  7588           if (newOffset < 0 ) {
       
  7589             newOffset = timeOffset;
       
  7590           }
       
  7591 
       
  7592           retVal.push.apply( retVal, parseChildren( currNode, newOffset, currRegion ) );
       
  7593         }
       
  7594       }
       
  7595 
       
  7596       currNode = currNode.nextSibling;
       
  7597     }
       
  7598 
       
  7599     return retVal;
       
  7600   }
       
  7601 
       
  7602   // Get the "region" attribute of a node, to know where to put the subtitles
       
  7603   function getNodeRegion( node, defaultTo ) {
       
  7604     var region = node.getAttribute( "region" );
       
  7605 
       
  7606     if ( region !== null ) {
       
  7607       return region;
       
  7608     } else {
       
  7609       return defaultTo || "";
       
  7610     }
       
  7611   }
       
  7612 
       
  7613   // Parse a node for text content
       
  7614   function parseNode( node, timeOffset, region ) {
       
  7615     var sub = {};
       
  7616 
       
  7617     // Trim left and right whitespace from text and convert non-explicit line breaks
       
  7618     sub.text = ( node.textContent || node.text ).replace( rWhitespace, "" ).replace( rLineBreak, "<br />" );
       
  7619     sub.id = node.getAttribute( "xml:id" ) || node.getAttribute( "id" );
       
  7620     sub.start = toSeconds ( node.getAttribute( "begin" ), timeOffset );
       
  7621     sub.end = toSeconds( node.getAttribute( "end" ), timeOffset );
       
  7622     sub.target = getNodeRegion( node, region );
       
  7623 
       
  7624     if ( sub.end < 0 ) {
       
  7625       // No end given, infer duration if possible
       
  7626       // Otherwise, give end as MAX_VALUE
       
  7627       sub.end = toSeconds( node.getAttribute( "duration" ), 0 );
       
  7628 
       
  7629       if ( sub.end >= 0 ) {
       
  7630         sub.end += sub.start;
       
  7631       } else {
       
  7632         sub.end = Number.MAX_VALUE;
       
  7633       }
       
  7634     }
       
  7635 
       
  7636     return { subtitle : sub };
       
  7637   }
       
  7638 
       
  7639   // Convert time expression to SS.mmm
       
  7640   // Expression may be absolute to timeline (hh:mm:ss.ms)
       
  7641   //   or relative ( decimal followed by metric ) ex: 3.4s, 5.7m
       
  7642   // Returns -1 if invalid
       
  7643   function toSeconds( t_in, offset ) {
       
  7644     var i;
       
  7645 
       
  7646     if ( !t_in ) {
       
  7647       return -1;
       
  7648     }
       
  7649 
       
  7650     try {
       
  7651       return Popcorn.util.toSeconds( t_in );
       
  7652     } catch ( e ) {
       
  7653       i = getMetricIndex( t_in );
       
  7654       return parseFloat( t_in.substring( 0, i ) ) * getMultipler( t_in.substring( i ) ) + ( offset || 0 );
       
  7655     }
       
  7656   }
       
  7657 
       
  7658   // In a time string such as 3.4ms, get the index of the first character (m) of the time metric (ms)
       
  7659   function getMetricIndex( t_in ) {
       
  7660     var i = t_in.length - 1;
       
  7661 
       
  7662     while ( i >= 0 && t_in[ i ] <= "9" && t_in[ i ] >= "0" ) {
       
  7663       i--;
       
  7664     }
       
  7665 
       
  7666     return i;
       
  7667   }
       
  7668 
       
  7669   // Determine multiplier for metric relative to seconds
       
  7670   function getMultipler( metric ) {
       
  7671     return {
       
  7672       "h" : 3600,
       
  7673       "m" : 60,
       
  7674       "s" : 1,
       
  7675       "ms" : 0.001
       
  7676     }[ metric ] || -1;
       
  7677   }
       
  7678 })( Popcorn );
       
  7679 // PARSER: 0.1 TTXT
       
  7680 
       
  7681 (function (Popcorn) {
       
  7682 
       
  7683   /**
       
  7684    * TTXT popcorn parser plug-in 
       
  7685    * Parses subtitle files in the TTXT format.
       
  7686    * Style information is ignored.
       
  7687    * Data parameter is given by Popcorn, will need an xml.
       
  7688    * Xml is the file contents to be parsed as a DOM tree
       
  7689    * 
       
  7690    * @param {Object} data
       
  7691    * 
       
  7692    * Example:
       
  7693      <TextSample sampleTime="00:00:00.000" text=""></TextSample>
       
  7694    */
       
  7695   Popcorn.parser( "parseTTXT", function( data ) {
       
  7696 
       
  7697     // declare needed variables
       
  7698     var returnData = {
       
  7699           title: "",
       
  7700           remote: "",
       
  7701           data: []
       
  7702         };
       
  7703 
       
  7704     // Simple function to convert HH:MM:SS.MMM to SS.MMM
       
  7705     // Assume valid, returns 0 on error
       
  7706     var toSeconds = function(t_in) {
       
  7707       var t = t_in.split(":");
       
  7708       var time = 0;
       
  7709       
       
  7710       try {        
       
  7711         return parseFloat(t[0], 10)*60*60 + parseFloat(t[1], 10)*60 + parseFloat(t[2], 10);
       
  7712       } catch (e) { time = 0; }
       
  7713       
       
  7714       return time;
       
  7715     };
       
  7716 
       
  7717     // creates an object of all atrributes keyed by name
       
  7718     var createTrack = function( name, attributes ) {
       
  7719       var track = {};
       
  7720       track[name] = attributes;
       
  7721       return track;
       
  7722     };
       
  7723 
       
  7724     // this is where things actually start
       
  7725     var node = data.xml.lastChild.lastChild; // Last Child of TextStreamHeader
       
  7726     var lastStart = Number.MAX_VALUE;
       
  7727     var cmds = [];
       
  7728     
       
  7729     // Work backwards through DOM, processing TextSample nodes
       
  7730     while (node) {
       
  7731       if ( node.nodeType === 1 && node.nodeName === "TextSample") {
       
  7732         var sub = {};
       
  7733         sub.start = toSeconds(node.getAttribute('sampleTime'));
       
  7734         sub.text = node.getAttribute('text');
       
  7735       
       
  7736         if (sub.text) { // Only process if text to display
       
  7737           // Infer end time from prior element, ms accuracy
       
  7738           sub.end = lastStart - 0.001;
       
  7739           cmds.push( createTrack("subtitle", sub) );
       
  7740         }
       
  7741         lastStart = sub.start;
       
  7742       }
       
  7743       node = node.previousSibling;
       
  7744     }
       
  7745     
       
  7746     returnData.data = cmds.reverse();
       
  7747 
       
  7748     return returnData;
       
  7749   });
       
  7750 
       
  7751 })( Popcorn );
       
  7752 // PARSER: 0.3 WebSRT/VTT
       
  7753 
       
  7754 (function ( Popcorn ) {
       
  7755   /**
       
  7756    * WebVTT popcorn parser plug-in
       
  7757    * Parses subtitle files in the WebVTT format.
       
  7758    * Specification here: http://www.whatwg.org/specs/web-apps/current-work/webvtt.html
       
  7759    * Styles which appear after timing information are presently ignored.
       
  7760    * Inline styling tags follow HTML conventions and are left in for the browser to handle (or ignore if VTT-specific)
       
  7761    * Data parameter is given by Popcorn, text property holds file contents.
       
  7762    * Text is the file contents to be parsed
       
  7763    *
       
  7764    * @param {Object} data
       
  7765    *
       
  7766    * Example:
       
  7767     00:32.500 --> 00:00:33.500 A:start S:50% D:vertical L:98%
       
  7768     <v Neil DeGrass Tyson><i>Laughs</i>
       
  7769    */
       
  7770   Popcorn.parser( "parseVTT", function( data ) {
       
  7771 
       
  7772     // declare needed variables
       
  7773     var retObj = {
       
  7774           title: "",
       
  7775           remote: "",
       
  7776           data: []
       
  7777         },
       
  7778         subs = [],
       
  7779         i = 0,
       
  7780         len = 0,
       
  7781         lines,
       
  7782         text,
       
  7783         sub,
       
  7784         rNewLine = /(?:\r\n|\r|\n)/gm;
       
  7785 
       
  7786     // Here is where the magic happens
       
  7787     // Split on line breaks
       
  7788     lines = data.text.split( rNewLine );
       
  7789     len = lines.length;
       
  7790 
       
  7791     // Check for BOF token
       
  7792     if ( len === 0 || lines[ 0 ] !== "WEBVTT" ) {
       
  7793       return retObj;
       
  7794     }
       
  7795 
       
  7796     i++;
       
  7797 
       
  7798     while ( i < len ) {
       
  7799       text = [];
       
  7800 
       
  7801       try {
       
  7802         i = skipWhitespace( lines, len, i );
       
  7803         sub = parseCueHeader( lines[ i++ ] );
       
  7804 
       
  7805         // Build single line of text from multi-line subtitle in file
       
  7806         while ( i < len && lines[ i ] ) {
       
  7807           text.push( lines[ i++ ] );
       
  7808         }
       
  7809 
       
  7810         // Join lines together to one and build subtitle text
       
  7811         sub.text = text.join( "<br />" );
       
  7812         subs.push( createTrack( "subtitle", sub ) );
       
  7813       } catch ( e ) {
       
  7814         i = skipNonWhitespace( lines, len, i );
       
  7815       }
       
  7816     }
       
  7817 
       
  7818     retObj.data = subs;
       
  7819     return retObj;
       
  7820   });
       
  7821 
       
  7822   // [HH:]MM:SS.mmm string to SS.mmm float
       
  7823   // Throws exception if invalid
       
  7824   function toSeconds ( t_in ) {
       
  7825     var t = t_in.split( ":" ),
       
  7826         l = t_in.length,
       
  7827         time;
       
  7828 
       
  7829     // Invalid time string provided
       
  7830     if ( l !== 12 && l !== 9 ) {
       
  7831       throw "Bad cue";
       
  7832     }
       
  7833 
       
  7834     l = t.length - 1;
       
  7835 
       
  7836     try {
       
  7837       time = parseInt( t[ l-1 ], 10 ) * 60 + parseFloat( t[ l ], 10 );
       
  7838 
       
  7839       // Hours were given
       
  7840       if ( l === 2 ) {
       
  7841         time += parseInt( t[ 0 ], 10 ) * 3600;
       
  7842       }
       
  7843     } catch ( e ) {
       
  7844       throw "Bad cue";
       
  7845     }
       
  7846 
       
  7847     return time;
       
  7848   }
       
  7849 
       
  7850   function createTrack( name, attributes ) {
       
  7851     var track = {};
       
  7852     track[ name ] = attributes;
       
  7853     return track;
       
  7854   }
       
  7855 
       
  7856   function parseCueHeader ( line ) {
       
  7857     var lineSegments,
       
  7858         args,
       
  7859         sub = {},
       
  7860         rToken = /-->/,
       
  7861         rWhitespace = /[\t ]+/;
       
  7862 
       
  7863     if ( !line || line.indexOf( "-->" ) === -1 ) {
       
  7864       throw "Bad cue";
       
  7865     }
       
  7866 
       
  7867     lineSegments = line.replace( rToken, " --> " ).split( rWhitespace );
       
  7868 
       
  7869     if ( lineSegments.length < 2 ) {
       
  7870       throw "Bad cue";
       
  7871     }
       
  7872 
       
  7873     sub.id = line;
       
  7874     sub.start = toSeconds( lineSegments[ 0 ] );
       
  7875     sub.end = toSeconds( lineSegments[ 2 ] );
       
  7876 
       
  7877     return sub;
       
  7878   }
       
  7879 
       
  7880   function skipWhitespace ( lines, len, i ) {
       
  7881     while ( i < len && !lines[ i ] ) {
       
  7882       i++;
       
  7883     }
       
  7884 
       
  7885     return i;
       
  7886   }
       
  7887 
       
  7888   function skipNonWhitespace ( lines, len, i ) {
       
  7889     while ( i < len && lines[ i ] ) {
       
  7890       i++;
       
  7891     }
       
  7892 
       
  7893     return i;
       
  7894   }
       
  7895 })( Popcorn );
       
  7896 // PARSER: 0.1 XML
       
  7897 
       
  7898 (function (Popcorn) {
       
  7899 
       
  7900   /**
       
  7901    *
       
  7902    *
       
  7903    */
       
  7904   Popcorn.parser( "parseXML", "XML", function( data ) {
       
  7905 
       
  7906     // declare needed variables
       
  7907     var returnData = {
       
  7908           title: "",
       
  7909           remote: "",
       
  7910           data: []
       
  7911         },
       
  7912         manifestData = {};
       
  7913 
       
  7914     // Simple function to convert 0:05 to 0.5 in seconds
       
  7915     // acceptable formats are HH:MM:SS:MM, MM:SS:MM, SS:MM, SS
       
  7916     var toSeconds = function(time) {
       
  7917       var t = time.split(":");
       
  7918       if (t.length === 1) {
       
  7919         return parseFloat(t[0], 10);
       
  7920       } else if (t.length === 2) {
       
  7921         return parseFloat(t[0], 10) + parseFloat(t[1] / 12, 10);
       
  7922       } else if (t.length === 3) {
       
  7923         return parseInt(t[0] * 60, 10) + parseFloat(t[1], 10) + parseFloat(t[2] / 12, 10);
       
  7924       } else if (t.length === 4) {
       
  7925         return parseInt(t[0] * 3600, 10) + parseInt(t[1] * 60, 10) + parseFloat(t[2], 10) + parseFloat(t[3] / 12, 10);
       
  7926       }
       
  7927     };
       
  7928 
       
  7929     // turns a node tree element into a straight up javascript object
       
  7930     // also converts in and out to start and end
       
  7931     // also links manifest data with ids
       
  7932     var objectifyAttributes = function ( nodeAttributes ) {
       
  7933 
       
  7934       var returnObject = {};
       
  7935 
       
  7936       for ( var i = 0, nal = nodeAttributes.length; i < nal; i++ ) {
       
  7937 
       
  7938         var key  = nodeAttributes.item(i).nodeName,
       
  7939             data = nodeAttributes.item(i).nodeValue,
       
  7940             manifestItem = manifestData[ data ];
       
  7941 
       
  7942         // converts in into start
       
  7943         if (key === "in") {
       
  7944           returnObject.start = toSeconds( data );
       
  7945         // converts out into end
       
  7946         } else if ( key === "out" ){
       
  7947           returnObject.end = toSeconds( data );
       
  7948         // this is where ids in the manifest are linked
       
  7949         } else if ( key === "resourceid" ) {
       
  7950           for ( var item in manifestItem ) {
       
  7951             if ( manifestItem.hasOwnProperty( item ) ) {
       
  7952               if ( !returnObject[ item ] && item !== "id" ) {
       
  7953                 returnObject[ item ] = manifestItem[ item ];
       
  7954               }
       
  7955             }
       
  7956           }
       
  7957         // everything else
       
  7958         } else {
       
  7959           returnObject[key] = data;
       
  7960         }
       
  7961 
       
  7962       }
       
  7963 
       
  7964       return returnObject;
       
  7965     };
       
  7966 
       
  7967     // creates an object of all atrributes keyd by name
       
  7968     var createTrack = function( name, attributes ) {
       
  7969       var track = {};
       
  7970       track[name] = attributes;
       
  7971       return track;
       
  7972     };
       
  7973 
       
  7974     // recursive function to process a node, or process the next child node
       
  7975     var parseNode = function ( node, allAttributes, manifest ) {
       
  7976       var attributes = {};
       
  7977       Popcorn.extend( attributes, allAttributes, objectifyAttributes( node.attributes ), { text: node.textContent || node.text } );
       
  7978 
       
  7979       var childNodes = node.childNodes;
       
  7980 
       
  7981       // processes the node
       
  7982       if ( childNodes.length < 1 || ( childNodes.length === 1 && childNodes[0].nodeType === 3 ) ) {
       
  7983 
       
  7984         if ( !manifest ) {
       
  7985           returnData.data.push( createTrack( node.nodeName, attributes ) );
       
  7986         } else {
       
  7987           manifestData[attributes.id] = attributes;
       
  7988         }
       
  7989 
       
  7990       // process the next child node
       
  7991       } else {
       
  7992 
       
  7993         for ( var i = 0; i < childNodes.length; i++ ) {
       
  7994 
       
  7995           if ( childNodes[i].nodeType === 1 ) {
       
  7996             parseNode( childNodes[i], attributes, manifest );
       
  7997           }
       
  7998 
       
  7999         }
       
  8000       }
       
  8001     };
       
  8002 
       
  8003     // this is where things actually start
       
  8004     var x = data.documentElement.childNodes;
       
  8005 
       
  8006     for ( var i = 0, xl = x.length; i < xl; i++ ) {
       
  8007 
       
  8008       if ( x[i].nodeType === 1 ) {
       
  8009 
       
  8010         // start the process of each main node type, manifest or timeline
       
  8011         if ( x[i].nodeName === "manifest" ) {
       
  8012           parseNode( x[i], {}, true );
       
  8013         } else { // timeline
       
  8014           parseNode( x[i], {}, false );
       
  8015         }
       
  8016 
       
  8017       }
       
  8018     }
       
  8019 
       
  8020     return returnData;
       
  8021   });
       
  8022 
       
  8023 })( Popcorn );
       
  8024 (function() {
       
  8025 
       
  8026   var scriptLoaded = false,
       
  8027       loading = false;
       
  8028   Popcorn.player( "soundcloud", {
       
  8029     _canPlayType: function( nodeName, url ) {
       
  8030 
       
  8031       return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(soundcloud)/).test( url ) && nodeName.toLowerCase() !== "video";
       
  8032     },
       
  8033     _setup: function( options ) {
       
  8034 
       
  8035       var media = this,
       
  8036           container = document.createElement( "iframe" ),
       
  8037           lastVolume = 1,
       
  8038           currentTime = 0,
       
  8039           paused = true,
       
  8040           realPaused = true,
       
  8041           widget,
       
  8042           duration = 0,
       
  8043           muted = false,
       
  8044           playerQueue = Popcorn.player.playerQueue();
       
  8045 
       
  8046       options._container = container;
       
  8047       media.style.visibility = "hidden";
       
  8048 
       
  8049       media.play = function() {
       
  8050 
       
  8051         paused = false;
       
  8052         playerQueue.add(function() {
       
  8053 
       
  8054           if ( realPaused ) {
       
  8055 
       
  8056             widget && widget.play();
       
  8057           } else {
       
  8058             playerQueue.next();
       
  8059           }
       
  8060         });
       
  8061       };
       
  8062 
       
  8063       media.pause = function() {
       
  8064 
       
  8065         paused = true;
       
  8066 
       
  8067         playerQueue.add(function() {
       
  8068 
       
  8069           if ( !realPaused ) {
       
  8070 
       
  8071             widget && widget.pause();
       
  8072           } else {
       
  8073             playerQueue.next();
       
  8074           }
       
  8075         });
       
  8076       };
       
  8077 
       
  8078       // getter and setter for muted property, multiply volume by 100 as that is the scale soundcloud works on
       
  8079       Object.defineProperties( media, {
       
  8080         muted: {
       
  8081           set: function( val ) {
       
  8082             if ( val ) {
       
  8083               widget && widget.getVolume(function( data ) {
       
  8084                 lastVolume = data / 100;
       
  8085               });
       
  8086               widget && widget.setVolume( 0 );
       
  8087               muted = true;
       
  8088             } else {
       
  8089               widget && widget.setVolume( lastVolume * 100 );
       
  8090               muted = false;
       
  8091             }
       
  8092             media.dispatchEvent( "volumechange" );
       
  8093           },
       
  8094           get: function() {
       
  8095             return muted;
       
  8096           }
       
  8097         },
       
  8098         volume: {
       
  8099           set: function( val ) {
       
  8100             widget && widget.setVolume( val * 100 );
       
  8101             lastVolume = val ;
       
  8102             media.dispatchEvent( "volumechange" );
       
  8103           },
       
  8104           get: function() {
       
  8105             return muted ? 0 : lastVolume;
       
  8106           }
       
  8107         },
       
  8108         currentTime: {
       
  8109           set: function( val ) {
       
  8110             currentTime = val;
       
  8111             widget && widget.seekTo( val * 1000 );
       
  8112             media.dispatchEvent( "seeked" );
       
  8113             media.dispatchEvent( "timeupdate" );
       
  8114           },
       
  8115           get: function() {
       
  8116             return currentTime;
       
  8117           }
       
  8118         },
       
  8119         duration: {
       
  8120           get: function() {
       
  8121             return duration;
       
  8122           }
       
  8123         },
       
  8124         paused: {
       
  8125           get: function() {
       
  8126             return paused;
       
  8127           }
       
  8128         }
       
  8129       });
       
  8130       // called when the SoundCloud api script has loaded
       
  8131       function scriptReady() {
       
  8132         scriptLoaded = true;
       
  8133 
       
  8134         SC.initialize({
       
  8135           client_id: "PRaNFlda6Bhf5utPjUsptg"
       
  8136         });
       
  8137 
       
  8138         SC.get( "/resolve", {
       
  8139           url: media.src
       
  8140         }, function( data ) {
       
  8141           media.width = media.style.width ? "" + media.offsetWidth : "560";
       
  8142           media.height = media.style.height ? "" + media.offsetHeight : "315";
       
  8143           // TODO: There are quite a few options here that we should pass on to the user
       
  8144           container.scrolling = "no";
       
  8145           container.frameborder = "no";
       
  8146           container.id = "soundcloud-" + Popcorn.guid();
       
  8147           container.src = "http://w.soundcloud.com/player/?url=" + data.uri +
       
  8148           "&show_artwork=false" +
       
  8149           "&buying=false" +
       
  8150           "&liking=false" +
       
  8151           "&sharing=false";
       
  8152 
       
  8153           container.width = "100%";
       
  8154           container.height = "100%";
       
  8155 
       
  8156           options.loadListener = function( e ) {
       
  8157             options.widget = widget = SC.Widget( container.id );
       
  8158             // setup all of our listeners
       
  8159             widget.bind(SC.Widget.Events.FINISH, function() {
       
  8160               media.pause();
       
  8161 
       
  8162               media.dispatchEvent( "ended" );
       
  8163             });
       
  8164 
       
  8165             widget.bind(SC.Widget.Events.PLAY_PROGRESS, function( data ) {
       
  8166 
       
  8167               currentTime = data.currentPosition / 1000;
       
  8168               media.dispatchEvent( "timeupdate" );
       
  8169             });
       
  8170 
       
  8171             widget.bind(SC.Widget.Events.PLAY, function( data ) {
       
  8172 
       
  8173               paused = realPaused = false;
       
  8174 
       
  8175               media.dispatchEvent( "play" );
       
  8176               media.dispatchEvent( "playing" );
       
  8177               media.currentTime = currentTime;
       
  8178 
       
  8179               playerQueue.next();
       
  8180             });
       
  8181 
       
  8182             widget.bind(SC.Widget.Events.PAUSE, function( data ) {
       
  8183 
       
  8184               paused = realPaused = true;
       
  8185               media.dispatchEvent( "pause" );
       
  8186 
       
  8187               playerQueue.next();
       
  8188             });
       
  8189             widget.bind(SC.Widget.Events.READY, function( data ) {
       
  8190               widget.getDuration(function( data ) {
       
  8191 
       
  8192                 duration = data / 1000;
       
  8193 
       
  8194                 media.style.visibility = "visible";
       
  8195                 media.dispatchEvent( "durationchange" );
       
  8196                 // update the readyState after we have the duration
       
  8197                 media.readyState = 4;
       
  8198                 media.dispatchEvent( "readystatechange" );
       
  8199                 media.dispatchEvent( "loadedmetadata" );
       
  8200                 media.dispatchEvent( "loadeddata" );
       
  8201                 media.dispatchEvent( "canplaythrough" );
       
  8202                 media.dispatchEvent( "load" );
       
  8203                 !media.paused && media.play();
       
  8204               });
       
  8205               widget.getVolume(function( data ) {
       
  8206                 lastVolume = data / 100;
       
  8207               });
       
  8208             });
       
  8209           };
       
  8210 
       
  8211           container.addEventListener( "load", options.loadListener, false );
       
  8212           media.appendChild( container );
       
  8213         });
       
  8214       }
       
  8215 
       
  8216       // load the SoundCloud API script if it doesn't exist
       
  8217       function loadScript() {
       
  8218         if ( !loading ) {
       
  8219           loading = true;
       
  8220           Popcorn.getScript( "http://w.soundcloud.com/player/api.js", function() {
       
  8221             Popcorn.getScript( "http://connect.soundcloud.com/sdk.js", function() {
       
  8222               scriptReady();
       
  8223             });
       
  8224           });
       
  8225         } else {
       
  8226           (function isReady() {
       
  8227             setTimeout(function() {
       
  8228               if ( !scriptLoaded ) {
       
  8229                 isReady();
       
  8230               } else {
       
  8231                 scriptReady();
       
  8232               }
       
  8233             }, 100 );
       
  8234           })();
       
  8235         }
       
  8236       }
       
  8237 
       
  8238       if ( !scriptLoaded ) {
       
  8239         loadScript();
       
  8240       } else {
       
  8241         scriptReady();
       
  8242       }
       
  8243     },
       
  8244     _teardown: function( options ) {
       
  8245       var widget = options.widget,
       
  8246           events = SC.Widget.Events,
       
  8247           container = options._container,
       
  8248           parentContainer = container.parentNode;
       
  8249 
       
  8250       options.destroyed = true;
       
  8251 
       
  8252       // if the widget never got setup, remove the containers load listener and return
       
  8253       if ( !widget ) {
       
  8254         container.removeEventListener( "load", options.loadEventListener, false );
       
  8255         return;
       
  8256       }
       
  8257 
       
  8258       // remove all bound soundcloud listeners
       
  8259       for ( var prop in events ) {
       
  8260         widget && widget.unbind( events[ prop ] );
       
  8261       }
       
  8262     }
       
  8263   });
       
  8264 })();
       
  8265 (function() {
       
  8266 
       
  8267   // parseUri 1.2.2
       
  8268   // http://blog.stevenlevithan.com/archives/parseuri
       
  8269   // (c) Steven Levithan <stevenlevithan.com>
       
  8270   // MIT License
       
  8271 
       
  8272   function parseUri (str) {
       
  8273     var	o   = parseUri.options,
       
  8274         m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
       
  8275         uri = {},
       
  8276         i   = 14;
       
  8277 
       
  8278     while (i--) {
       
  8279       uri[o.key[i]] = m[i] || "";
       
  8280     }
       
  8281 
       
  8282     uri[o.q.name] = {};
       
  8283     uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
       
  8284       if ($1) {
       
  8285         uri[o.q.name][$1] = $2;
       
  8286       }
       
  8287     });
       
  8288 
       
  8289     return uri;
       
  8290   }
       
  8291 
       
  8292   parseUri.options = {
       
  8293     strictMode: false,
       
  8294     key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
       
  8295     q:   {
       
  8296       name:   "queryKey",
       
  8297       parser: /(?:^|&)([^&=]*)=?([^&]*)/g
       
  8298     },
       
  8299     parser: {
       
  8300       strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
       
  8301       loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
       
  8302     }
       
  8303   };
       
  8304 
       
  8305   function canPlayType( nodeName, url ) {
       
  8306     return ( /player.vimeo.com\/video\/\d+/ ).test( url ) ||
       
  8307            ( /vimeo.com\/\d+/ ).test( url );
       
  8308   }
       
  8309 
       
  8310   Popcorn.player( "vimeo", {
       
  8311 
       
  8312     _canPlayType: canPlayType,
       
  8313     _setup: function( options ) {
       
  8314 
       
  8315       var TIMEUPDATE_INTERVAL_MS  = 250,
       
  8316           CURRENT_TIME_MONITOR_MS = 16,
       
  8317           MediaErrorInterface = {
       
  8318             MEDIA_ERR_ABORTED: 1,
       
  8319             MEDIA_ERR_NETWORK: 2,
       
  8320             MEDIA_ERR_DECODE: 3,
       
  8321             MEDIA_ERR_SRC_NOT_SUPPORTED: 4
       
  8322           },
       
  8323           guid,
       
  8324           media = this,
       
  8325           commands = {
       
  8326             q: [],
       
  8327             queue: function queue( fn ) {
       
  8328               this.q.push( fn );
       
  8329               this.process();
       
  8330             },
       
  8331             process: function process() {
       
  8332               if ( !vimeoReady ) {
       
  8333                 return;
       
  8334               }
       
  8335 
       
  8336               while ( this.q.length ) {
       
  8337                 var fn = this.q.shift();
       
  8338                 fn();
       
  8339               }
       
  8340             }
       
  8341           },
       
  8342           currentTimeId,
       
  8343           timeUpdateId,
       
  8344           vimeoReady,
       
  8345           vimeoContainer = document.createElement( "iframe" ),
       
  8346           // Loosely based on HTMLMediaElement + HTMLVideoElement IDL
       
  8347           impl = {
       
  8348             // error state
       
  8349             error: null,
       
  8350 
       
  8351             // network state
       
  8352             src: media.src,
       
  8353             NETWORK_EMPTY: 0,
       
  8354             NETWORK_IDLE: 1,
       
  8355             NETWORK_LOADING: 2,
       
  8356             NETWORK_NO_SOURCE: 3,
       
  8357             networkState: 0,
       
  8358 
       
  8359             // ready state
       
  8360             HAVE_NOTHING: 0,
       
  8361             HAVE_METADATA: 1,
       
  8362             HAVE_CURRENT_DATA: 2,
       
  8363             HAVE_FUTURE_DATA: 3,
       
  8364             HAVE_ENOUGH_DATA: 4,
       
  8365             readyState: 0,
       
  8366             seeking: false,
       
  8367 
       
  8368             // playback state
       
  8369             currentTime: 0,
       
  8370             duration: NaN,
       
  8371             paused: true,
       
  8372             ended: false,
       
  8373             autoplay: false,
       
  8374             loop: false,
       
  8375 
       
  8376             // controls
       
  8377             volume: 1,
       
  8378             muted: false,
       
  8379 
       
  8380             // Video attributes
       
  8381             width: 0,
       
  8382             height: 0
       
  8383           };
       
  8384 
       
  8385       var readOnlyAttrs = "error networkState readyState seeking duration paused ended";
       
  8386       Popcorn.forEach( readOnlyAttrs.split(" "), function( value ) {
       
  8387         Object.defineProperty( media, value, {
       
  8388           get: function() {
       
  8389             return impl[ value ];
       
  8390           }
       
  8391         });
       
  8392       });
       
  8393 
       
  8394       Object.defineProperties( media, {
       
  8395         "src": {
       
  8396           get: function() {
       
  8397             return impl.src;
       
  8398           },
       
  8399           set: function( value ) {
       
  8400             // Is there any sort of logic that determines whether to load the video or not?
       
  8401             impl.src = value;
       
  8402             media.load();
       
  8403           }
       
  8404         },
       
  8405         "currentTime": {
       
  8406           get: function() {
       
  8407             return impl.currentTime;
       
  8408           },
       
  8409           set: function( value ) {
       
  8410             commands.queue(function() {
       
  8411               sendMessage( "seekTo", value );
       
  8412             });
       
  8413             impl.seeking = true;
       
  8414             media.dispatchEvent( "seeking" );
       
  8415           }
       
  8416         },
       
  8417         "autoplay": {
       
  8418           get: function() {
       
  8419             return impl.autoplay;
       
  8420           },
       
  8421           set: function( value ) {
       
  8422             impl.autoplay = !!value;
       
  8423           }
       
  8424         },
       
  8425         "loop": {
       
  8426           get: function() {
       
  8427             return impl.loop;
       
  8428           },
       
  8429           set: function( value) {
       
  8430             impl.loop = !!value;
       
  8431             commands.queue(function() {
       
  8432               sendMessage( "setLoop", loop );
       
  8433             });
       
  8434           }
       
  8435         },
       
  8436         "volume": {
       
  8437           get: function() {
       
  8438             return impl.volume;
       
  8439           },
       
  8440           set: function( value ) {
       
  8441             impl.volume = value;
       
  8442             commands.queue(function() {
       
  8443               sendMessage( "setVolume", impl.muted ? 0 : impl.volume );
       
  8444             });
       
  8445             media.dispatchEvent( "volumechange" );
       
  8446           }
       
  8447         },
       
  8448         "muted": {
       
  8449           get: function() {
       
  8450             return impl.muted;
       
  8451           },
       
  8452           set: function( value ) {
       
  8453             impl.muted = !!value;
       
  8454             commands.queue(function() {
       
  8455               sendMessage( "setVolume", impl.muted ? 0 : impl.volume );
       
  8456             });
       
  8457             media.dispatchEvent( "volumechange" );
       
  8458           }
       
  8459         },
       
  8460         "width": {
       
  8461           get: function() {
       
  8462             return vimeoContainer.width;
       
  8463           },
       
  8464           set: function( value ) {
       
  8465             vimeoContainer.width = value;
       
  8466           }
       
  8467         },
       
  8468         "height": {
       
  8469           get: function() {
       
  8470             return vimeoContainer.height;
       
  8471           },
       
  8472           set: function( value ) {
       
  8473             vimeoContainer.height = value;
       
  8474           }
       
  8475         }
       
  8476       });
       
  8477 
       
  8478       function sendMessage( method, params ) {
       
  8479         var url = vimeoContainer.src.split( "?" )[ 0 ],
       
  8480             data = JSON.stringify({
       
  8481               method: method,
       
  8482               value: params
       
  8483             });
       
  8484 
       
  8485         if ( url.substr( 0, 2 ) === "//" ) {
       
  8486           url = window.location.protocol + url;
       
  8487         }
       
  8488 
       
  8489         // The iframe has been destroyed, it just doesn't know it
       
  8490         if ( !vimeoContainer.contentWindow ) {
       
  8491           media.unload();
       
  8492           return;
       
  8493         }
       
  8494 
       
  8495         vimeoContainer.contentWindow.postMessage( data, url );
       
  8496       }
       
  8497 
       
  8498       var vimeoAPIMethods = {
       
  8499         "getCurrentTime": function( data ) {
       
  8500           impl.currentTime = parseFloat( data.value );
       
  8501         },
       
  8502         "getDuration": function( data ) {
       
  8503           impl.duration = parseFloat( data.value );
       
  8504           maybeReady();
       
  8505         },
       
  8506         "getVolume": function( data ) {
       
  8507           impl.volume = parseFloat( data.value );
       
  8508         }
       
  8509       };
       
  8510 
       
  8511       var vimeoAPIEvents = {
       
  8512         "ready": function( data ) {
       
  8513           sendMessage( "addEventListener", "loadProgress" );
       
  8514           sendMessage( "addEventListener", "playProgress" );
       
  8515           sendMessage( "addEventListener", "play" );
       
  8516           sendMessage( "addEventListener", "pause" );
       
  8517           sendMessage( "addEventListener", "finish" );
       
  8518           sendMessage( "addEventListener", "seek" );
       
  8519           sendMessage( "getDuration" );
       
  8520           vimeoReady = true;
       
  8521           commands.process();
       
  8522           media.dispatchEvent( "loadstart" );
       
  8523         },
       
  8524         "loadProgress": function( data ) {
       
  8525           media.dispatchEvent( "progress" );
       
  8526           // loadProgress has a more accurate duration than getDuration
       
  8527           impl.duration = parseFloat( data.data.duration );
       
  8528         },
       
  8529         "playProgress": function( data ) {
       
  8530           impl.currentTime = parseFloat( data.data.seconds );
       
  8531         },
       
  8532         "play": function( data ) {
       
  8533           // Vimeo plays video if seeking from an unloaded state
       
  8534           if ( impl.seeking ) {
       
  8535             impl.seeking = false;
       
  8536             media.dispatchEvent( "seeked" );
       
  8537           }
       
  8538           impl.paused = false;
       
  8539           impl.ended = false;
       
  8540           startUpdateLoops();
       
  8541           media.dispatchEvent( "play" );
       
  8542         },
       
  8543         "pause": function( data ) {
       
  8544           impl.paused = true;
       
  8545           stopUpdateLoops();
       
  8546           media.dispatchEvent( "pause" );
       
  8547         },
       
  8548         "finish": function( data ) {
       
  8549           impl.ended = true;
       
  8550           stopUpdateLoops();
       
  8551           media.dispatchEvent( "ended" );
       
  8552         },
       
  8553         "seek": function( data ) {
       
  8554           impl.currentTime = parseFloat( data.data.seconds );
       
  8555           impl.seeking = false;
       
  8556           impl.ended = false;
       
  8557           media.dispatchEvent( "timeupdate" );
       
  8558           media.dispatchEvent( "seeked" );
       
  8559         }
       
  8560       };
       
  8561 
       
  8562       function messageListener( event ) {
       
  8563         if ( event.origin !== "http://player.vimeo.com" ) {
       
  8564           return;
       
  8565         }
       
  8566 
       
  8567         var data;
       
  8568         try {
       
  8569           data = JSON.parse( event.data );
       
  8570         } catch ( ex ) {
       
  8571           console.warn( ex );
       
  8572         }
       
  8573 
       
  8574         if ( data.player_id != guid ) {
       
  8575           return;
       
  8576         }
       
  8577 
       
  8578         // Methods
       
  8579         if ( data.method && vimeoAPIMethods[ data.method ] ) {
       
  8580           vimeoAPIMethods[ data.method ]( data );
       
  8581         }
       
  8582 
       
  8583         // Events
       
  8584         if ( data.event && vimeoAPIEvents[ data.event ] ) {
       
  8585           vimeoAPIEvents[ data.event ]( data );
       
  8586         }
       
  8587       }
       
  8588 
       
  8589       media.load = function() {
       
  8590         vimeoReady = false;
       
  8591         guid = Popcorn.guid();
       
  8592 
       
  8593         var src = parseUri( impl.src ),
       
  8594             combinedOptions = {},
       
  8595             optionsArray = [],
       
  8596             vimeoAPIOptions = {
       
  8597               api: 1,
       
  8598               player_id: guid
       
  8599             };
       
  8600 
       
  8601         if ( !canPlayType( media.nodeName, src.source ) ) {
       
  8602           setErrorAttr( impl.MEDIA_ERR_SRC_NOT_SUPPORTED );
       
  8603           return;
       
  8604         }
       
  8605 
       
  8606         // Add Popcorn ctor options, url options, then the Vimeo API options
       
  8607         Popcorn.extend( combinedOptions, options );
       
  8608         Popcorn.extend( combinedOptions, src.queryKey );
       
  8609         Popcorn.extend( combinedOptions, vimeoAPIOptions );
       
  8610 
       
  8611         // Create the base vimeo player string. It will always have query string options
       
  8612         src = "http://player.vimeo.com/video/" + ( /\d+$/ ).exec( src.path ) + "?";
       
  8613 
       
  8614         for ( var key in combinedOptions ) {
       
  8615           if ( combinedOptions.hasOwnProperty( key ) ) {
       
  8616             optionsArray.push( encodeURIComponent( key ) + "=" + encodeURIComponent( combinedOptions[ key ] ) );
       
  8617           }
       
  8618         }
       
  8619         src += optionsArray.join( "&" );
       
  8620 
       
  8621         impl.loop = !!src.match( /loop=1/ );
       
  8622         impl.autoplay = !!src.match( /autoplay=1/ );
       
  8623 
       
  8624         vimeoContainer.width = media.style.width ? media.style.width : 500;
       
  8625         vimeoContainer.height = media.style.height ? media.style.height : 281;
       
  8626         vimeoContainer.frameBorder = 0;
       
  8627         vimeoContainer.webkitAllowFullScreen = true;
       
  8628         vimeoContainer.mozAllowFullScreen = true;
       
  8629         vimeoContainer.allowFullScreen = true;
       
  8630         vimeoContainer.src = src;
       
  8631         media.appendChild( vimeoContainer );
       
  8632       };
       
  8633 
       
  8634       function setErrorAttr( value ) {
       
  8635         impl.error = {};
       
  8636         Popcorn.extend( impl.error, MediaErrorInterface );
       
  8637         impl.error.code = value;
       
  8638         media.dispatchEvent( "error" );
       
  8639       }
       
  8640 
       
  8641       function maybeReady() {
       
  8642         if ( !isNaN( impl.duration ) ) {
       
  8643           impl.readyState = 4;
       
  8644           media.dispatchEvent( "durationchange" );
       
  8645           media.dispatchEvent( "loadedmetadata" );
       
  8646           media.dispatchEvent( "loadeddata" );
       
  8647           media.dispatchEvent( "canplay" );
       
  8648           media.dispatchEvent( "canplaythrough" );
       
  8649         }
       
  8650       }
       
  8651 
       
  8652       function startUpdateLoops() {
       
  8653         if ( !timeUpdateId ) {
       
  8654           timeUpdateId = setInterval(function() {
       
  8655             media.dispatchEvent( "timeupdate" );
       
  8656           }, TIMEUPDATE_INTERVAL_MS );
       
  8657         }
       
  8658 
       
  8659         if ( !currentTimeId ) {
       
  8660           currentTimeId = setInterval(function() {
       
  8661             sendMessage( "getCurrentTime" );
       
  8662           }, CURRENT_TIME_MONITOR_MS );
       
  8663         }
       
  8664       }
       
  8665 
       
  8666       function stopUpdateLoops() {
       
  8667         if ( timeUpdateId ) {
       
  8668           clearInterval( timeUpdateId );
       
  8669           timeUpdateId = 0;
       
  8670         }
       
  8671 
       
  8672         if ( currentTimeId ) {
       
  8673           clearInterval( currentTimeId );
       
  8674           currentTimeId = 0;
       
  8675         }
       
  8676       }
       
  8677 
       
  8678       media.unload = function() {
       
  8679         stopUpdateLoops();
       
  8680         window.removeEventListener( "message", messageListener, false );
       
  8681       };
       
  8682 
       
  8683       media.play = function() {
       
  8684         commands.queue(function() {
       
  8685           sendMessage( "play" );
       
  8686         });
       
  8687       };
       
  8688 
       
  8689       media.pause = function() {
       
  8690         commands.queue(function() {
       
  8691           sendMessage( "pause" );
       
  8692         });
       
  8693       };
       
  8694 
       
  8695       // Start the load process now, players behave like `preload="metadata"` is set
       
  8696       // Do it asynchronously so that users can attach event listeners
       
  8697       setTimeout(function() {
       
  8698         window.addEventListener( "message", messageListener, false );
       
  8699         media.load();
       
  8700       }, 0 );
       
  8701     },
       
  8702     _teardown: function( options ) {
       
  8703       // If the baseplayer doesn't call _setup
       
  8704       if ( this.unload ) {
       
  8705         this.unload();
       
  8706       }
       
  8707     }
       
  8708   });
       
  8709 })();
       
  8710 (function( window, Popcorn ) {
       
  8711   // A global callback for youtube... that makes me angry
       
  8712   window.onYouTubePlayerAPIReady = function() {
       
  8713 
       
  8714     onYouTubePlayerAPIReady.ready = true;
       
  8715     for ( var i = 0; i < onYouTubePlayerAPIReady.waiting.length; i++ ) {
       
  8716       onYouTubePlayerAPIReady.waiting[ i ]();
       
  8717     }
       
  8718   };
       
  8719 
       
  8720   // existing youtube references can break us.
       
  8721   // remove it and use the one we can trust.
       
  8722   if ( window.YT ) {
       
  8723     window.quarantineYT = window.YT;
       
  8724     window.YT = null;
       
  8725   }
       
  8726 
       
  8727   onYouTubePlayerAPIReady.waiting = [];
       
  8728 
       
  8729   var _loading = false;
       
  8730 
       
  8731   Popcorn.player( "youtube", {
       
  8732     _canPlayType: function( nodeName, url ) {
       
  8733 
       
  8734       return typeof url === "string" && (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu)/).test( url ) && nodeName.toLowerCase() !== "video";
       
  8735     },
       
  8736     _setup: function( options ) {
       
  8737       if ( !window.YT && !_loading ) {
       
  8738         _loading = true;
       
  8739         Popcorn.getScript( "//youtube.com/player_api" );
       
  8740       }
       
  8741 
       
  8742       var media = this,
       
  8743           autoPlay = false,
       
  8744           container = document.createElement( "div" ),
       
  8745           currentTime = 0,
       
  8746           paused = true,
       
  8747           seekTime = 0,
       
  8748           firstGo = true,
       
  8749           seeking = false,
       
  8750           fragmentStart = 0,
       
  8751 
       
  8752           // state code for volume changed polling
       
  8753           lastMuted = false,
       
  8754           lastVolume = 100,
       
  8755           playerQueue = Popcorn.player.playerQueue();
       
  8756 
       
  8757       var createProperties = function() {
       
  8758 
       
  8759         Popcorn.player.defineProperty( media, "currentTime", {
       
  8760           set: function( val ) {
       
  8761 
       
  8762             if ( options.destroyed ) {
       
  8763               return;
       
  8764             }
       
  8765 
       
  8766             seeking = true;
       
  8767             // make sure val is a number
       
  8768             currentTime = Math.round( +val * 100 ) / 100;
       
  8769           },
       
  8770           get: function() {
       
  8771 
       
  8772             return currentTime;
       
  8773           }
       
  8774         });
       
  8775 
       
  8776         Popcorn.player.defineProperty( media, "paused", {
       
  8777           get: function() {
       
  8778 
       
  8779             return paused;
       
  8780           }
       
  8781         });
       
  8782 
       
  8783         Popcorn.player.defineProperty( media, "muted", {
       
  8784           set: function( val ) {
       
  8785 
       
  8786             if ( options.destroyed ) {
       
  8787 
       
  8788               return val;
       
  8789             }
       
  8790 
       
  8791             if ( options.youtubeObject.isMuted() !== val ) {
       
  8792 
       
  8793               if ( val ) {
       
  8794 
       
  8795                 options.youtubeObject.mute();
       
  8796               } else {
       
  8797 
       
  8798                 options.youtubeObject.unMute();
       
  8799               }
       
  8800 
       
  8801               lastMuted = options.youtubeObject.isMuted();
       
  8802               media.dispatchEvent( "volumechange" );
       
  8803             }
       
  8804 
       
  8805             return options.youtubeObject.isMuted();
       
  8806           },
       
  8807           get: function() {
       
  8808 
       
  8809             if ( options.destroyed ) {
       
  8810 
       
  8811               return 0;
       
  8812             }
       
  8813 
       
  8814             return options.youtubeObject.isMuted();
       
  8815           }
       
  8816         });
       
  8817 
       
  8818         Popcorn.player.defineProperty( media, "volume", {
       
  8819           set: function( val ) {
       
  8820 
       
  8821             if ( options.destroyed ) {
       
  8822 
       
  8823               return val;
       
  8824             }
       
  8825 
       
  8826             if ( options.youtubeObject.getVolume() / 100 !== val ) {
       
  8827 
       
  8828               options.youtubeObject.setVolume( val * 100 );
       
  8829               lastVolume = options.youtubeObject.getVolume();
       
  8830               media.dispatchEvent( "volumechange" );
       
  8831             }
       
  8832 
       
  8833             return options.youtubeObject.getVolume() / 100;
       
  8834           },
       
  8835           get: function() {
       
  8836 
       
  8837             if ( options.destroyed ) {
       
  8838 
       
  8839               return 0;
       
  8840             }
       
  8841 
       
  8842             return options.youtubeObject.getVolume() / 100;
       
  8843           }
       
  8844         });
       
  8845 
       
  8846         media.play = function() {
       
  8847 
       
  8848           if ( options.destroyed ) {
       
  8849 
       
  8850             return;
       
  8851           }
       
  8852 
       
  8853           paused = false;
       
  8854           playerQueue.add(function() {
       
  8855 
       
  8856             if ( options.youtubeObject.getPlayerState() !== 1 ) {
       
  8857 
       
  8858               seeking = false;
       
  8859               options.youtubeObject.playVideo();
       
  8860             } else {
       
  8861               playerQueue.next();
       
  8862             }
       
  8863           });
       
  8864         };
       
  8865 
       
  8866         media.pause = function() {
       
  8867 
       
  8868           if ( options.destroyed ) {
       
  8869 
       
  8870             return;
       
  8871           }
       
  8872 
       
  8873           paused = true;
       
  8874           playerQueue.add(function() {
       
  8875 
       
  8876             if ( options.youtubeObject.getPlayerState() !== 2 ) {
       
  8877 
       
  8878               options.youtubeObject.pauseVideo();
       
  8879             } else {
       
  8880               playerQueue.next();
       
  8881             }
       
  8882           });
       
  8883         };
       
  8884       };
       
  8885 
       
  8886       container.id = media.id + Popcorn.guid();
       
  8887       options._container = container;
       
  8888       media.appendChild( container );
       
  8889 
       
  8890       var youtubeInit = function() {
       
  8891 
       
  8892         var src, query, params, playerVars, queryStringItem, firstPlay = true;
       
  8893 
       
  8894         var timeUpdate = function() {
       
  8895 
       
  8896           if ( options.destroyed ) {
       
  8897             return;
       
  8898           }
       
  8899 
       
  8900           if ( !seeking ) {
       
  8901             currentTime = options.youtubeObject.getCurrentTime();
       
  8902             media.dispatchEvent( "timeupdate" );
       
  8903           } else if ( currentTime === options.youtubeObject.getCurrentTime() ) {
       
  8904 
       
  8905             seeking = false;
       
  8906             media.dispatchEvent( "seeked" );
       
  8907             media.dispatchEvent( "timeupdate" );
       
  8908           } else {
       
  8909 
       
  8910             // keep trying the seek until it is right.
       
  8911             options.youtubeObject.seekTo( currentTime );
       
  8912           }
       
  8913           setTimeout( timeUpdate, 250 );
       
  8914         };
       
  8915 
       
  8916         // delay is in seconds
       
  8917         var fetchDuration = function( delay ) {
       
  8918           var ytDuration = options.youtubeObject.getDuration();
       
  8919 
       
  8920           if ( isNaN( ytDuration ) || ytDuration === 0 ) {
       
  8921             setTimeout( function() {
       
  8922               fetchDuration( delay * 2 );
       
  8923             }, delay*1000 );
       
  8924           } else {
       
  8925             // set duration and dispatch ready events
       
  8926             media.duration = ytDuration;
       
  8927             media.dispatchEvent( "durationchange" );
       
  8928             
       
  8929             media.dispatchEvent( "loadedmetadata" );
       
  8930             media.dispatchEvent( "loadeddata" );
       
  8931             
       
  8932             media.readyState = 4;
       
  8933 
       
  8934             timeUpdate();
       
  8935             media.dispatchEvent( "canplaythrough" );
       
  8936           }
       
  8937         };
       
  8938 
       
  8939         options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1;
       
  8940         options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1;
       
  8941 
       
  8942         src = /^.*(?:\/|v=)(.{11})/.exec( media.src )[ 1 ];
       
  8943 
       
  8944         query = ( media.src.split( "?" )[ 1 ] || "" )
       
  8945                            .replace( /v=.{11}/, "" );
       
  8946         query = query.replace( /&t=(?:(\d+)m)?(?:(\d+)s)?/, function( all, minutes, seconds ) {
       
  8947 
       
  8948           // Make sure we have real zeros
       
  8949           minutes = minutes | 0; // bit-wise OR
       
  8950           seconds = seconds | 0; // bit-wise OR
       
  8951 
       
  8952           fragmentStart = ( +seconds + ( minutes * 60 ) );
       
  8953           return "";
       
  8954         });
       
  8955         query = query.replace( /&start=(\d+)?/, function( all, seconds ) {
       
  8956 
       
  8957           // Make sure we have real zeros
       
  8958           seconds = seconds | 0; // bit-wise OR
       
  8959 
       
  8960           fragmentStart = seconds;
       
  8961           return "";
       
  8962         });
       
  8963 
       
  8964         autoPlay = ( /autoplay=1/.test( query ) );
       
  8965 
       
  8966         params = query.split( /[\&\?]/g );
       
  8967         playerVars = { wmode: "transparent" };
       
  8968 
       
  8969         for( var i = 0; i < params.length; i++ ) {
       
  8970           queryStringItem = params[ i ].split( "=" );
       
  8971           playerVars[ queryStringItem[ 0 ] ] = queryStringItem[ 1 ];
       
  8972         }
       
  8973         
       
  8974         options.youtubeObject = new YT.Player( container.id, {
       
  8975           height: "100%",
       
  8976           width: "100%",
       
  8977           wmode: "transparent",
       
  8978           playerVars: playerVars,
       
  8979           videoId: src,
       
  8980           events: {
       
  8981             "onReady": function(){
       
  8982 
       
  8983               // pulling initial volume states form baseplayer
       
  8984               lastVolume = media.volume;
       
  8985               lastMuted = media.muted;
       
  8986 
       
  8987               volumeupdate();
       
  8988 
       
  8989               paused = media.paused;
       
  8990               createProperties();
       
  8991               options.youtubeObject.playVideo();
       
  8992 
       
  8993               media.currentTime = fragmentStart;
       
  8994               // wait to dispatch ready events until we get a duration
       
  8995             },
       
  8996             "onStateChange": function( state ){
       
  8997 
       
  8998               if ( options.destroyed || state.data === -1 ) {
       
  8999                 return;
       
  9000               }
       
  9001 
       
  9002               // state.data === 2 is for pause events
       
  9003               // state.data === 1 is for play events
       
  9004               if ( state.data === 2 ) {
       
  9005                 paused = true;
       
  9006                 media.dispatchEvent( "pause" );
       
  9007                 playerQueue.next();
       
  9008               } else if ( state.data === 1 && !firstPlay ) {
       
  9009                 paused = false;
       
  9010                 media.dispatchEvent( "play" );
       
  9011                 media.dispatchEvent( "playing" );
       
  9012                 playerQueue.next();
       
  9013               } else if ( state.data === 0 ) {
       
  9014                 media.dispatchEvent( "ended" );
       
  9015               } else if ( state.data === 1 && firstPlay ) {
       
  9016                 firstPlay = false;
       
  9017 
       
  9018                 // pulling initial paused state from autoplay or the baseplayer
       
  9019                 // also need to explicitly set to paused otherwise.
       
  9020                 if ( autoPlay || !media.paused ) {
       
  9021                   paused = false;
       
  9022                 }
       
  9023 
       
  9024                 if ( paused ) {
       
  9025                   options.youtubeObject.pauseVideo();
       
  9026                 }
       
  9027                 
       
  9028                 fetchDuration( 0.025 );
       
  9029               }
       
  9030             },
       
  9031             "onError": function( error ) {
       
  9032 
       
  9033               if ( [ 2, 100, 101, 150 ].indexOf( error.data ) !== -1 ) {
       
  9034                 media.error = {
       
  9035                   customCode: error.data
       
  9036                 };
       
  9037                 media.dispatchEvent( "error" );
       
  9038               }
       
  9039             }
       
  9040           }
       
  9041         });
       
  9042 
       
  9043         var volumeupdate = function() {
       
  9044 
       
  9045           if ( options.destroyed ) {
       
  9046 
       
  9047             return;
       
  9048           }
       
  9049 
       
  9050           if ( lastMuted !== options.youtubeObject.isMuted() ) {
       
  9051 
       
  9052             lastMuted = options.youtubeObject.isMuted();
       
  9053             media.dispatchEvent( "volumechange" );
       
  9054           }
       
  9055 
       
  9056           if ( lastVolume !== options.youtubeObject.getVolume() ) {
       
  9057 
       
  9058             lastVolume = options.youtubeObject.getVolume();
       
  9059             media.dispatchEvent( "volumechange" );
       
  9060           }
       
  9061 
       
  9062           setTimeout( volumeupdate, 250 );
       
  9063         };
       
  9064       };
       
  9065 
       
  9066       if ( onYouTubePlayerAPIReady.ready ) {
       
  9067 
       
  9068         youtubeInit();
       
  9069       } else {
       
  9070 
       
  9071         onYouTubePlayerAPIReady.waiting.push( youtubeInit );
       
  9072       }
       
  9073     },
       
  9074     _teardown: function( options ) {
       
  9075 
       
  9076       options.destroyed = true;
       
  9077 
       
  9078       var youtubeObject = options.youtubeObject;
       
  9079       if( youtubeObject ){
       
  9080         youtubeObject.stopVideo();
       
  9081         youtubeObject.clearVideo && youtubeObject.clearVideo();
       
  9082       }
       
  9083 
       
  9084       this.removeChild( document.getElementById( options._container.id ) );
       
  9085     }
       
  9086   });
       
  9087 }( window, Popcorn ));
       
  9088 // EFFECT: applyclass
       
  9089 
       
  9090 (function (Popcorn) {
       
  9091 
       
  9092   /**
       
  9093    * apply css class to jquery selector
       
  9094    * selector is relative to plugin target's id
       
  9095    * so .overlay is actually jQuery( "#target .overlay")
       
  9096    *
       
  9097    * @param {Object} options
       
  9098    * 
       
  9099    * Example:
       
  9100      var p = Popcorn('#video')
       
  9101         .footnote({
       
  9102           start: 5, // seconds
       
  9103           end: 15, // seconds
       
  9104           text: 'This video made exclusively for drumbeat.org',
       
  9105           target: 'footnotediv',
       
  9106           effect: 'applyclass',
       
  9107           applyclass: 'selector: class'
       
  9108         })
       
  9109    *
       
  9110    */
       
  9111 
       
  9112   var toggleClass = function( event, options ) {
       
  9113 
       
  9114     var idx = 0, len = 0, elements;
       
  9115 
       
  9116     Popcorn.forEach( options.classes, function( key, val ) {
       
  9117 
       
  9118       elements = [];
       
  9119 
       
  9120       if ( key === "parent" ) {
       
  9121 
       
  9122         elements[ 0 ] = document.querySelectorAll("#" + options.target )[ 0 ].parentNode;
       
  9123       } else {
       
  9124 
       
  9125         elements = document.querySelectorAll("#" + options.target + " " + key );
       
  9126       }
       
  9127 
       
  9128       for ( idx = 0, len = elements.length; idx < len; idx++ ) {
       
  9129 
       
  9130         elements[ idx ].classList.toggle( val );
       
  9131       }
       
  9132     });
       
  9133   };
       
  9134 
       
  9135   Popcorn.compose( "applyclass", {
       
  9136     
       
  9137     manifest: {
       
  9138       about: {
       
  9139         name: "Popcorn applyclass Effect",
       
  9140         version: "0.1",
       
  9141         author: "@scottdowne",
       
  9142         website: "scottdowne.wordpress.com"
       
  9143       },
       
  9144       options: {}
       
  9145     },
       
  9146     _setup: function( options ) {
       
  9147 
       
  9148       options.classes = {};
       
  9149       options.applyclass = options.applyclass || "";
       
  9150 
       
  9151       var classes = options.applyclass.replace( /\s/g, "" ).split( "," ),
       
  9152           item = [],
       
  9153           idx = 0, len = classes.length;
       
  9154 
       
  9155       for ( ; idx < len; idx++ ) {
       
  9156 
       
  9157         item = classes[ idx ].split( ":" );
       
  9158 
       
  9159         if ( item[ 0 ] ) {
       
  9160           options.classes[ item[ 0 ] ] = item[ 1 ] || "";
       
  9161         }
       
  9162       }
       
  9163     },
       
  9164     start: toggleClass,
       
  9165     end: toggleClass
       
  9166   });
       
  9167 })( Popcorn );