text: /*
   * popcorn.js version v1.2-244-gda087cb
   * http://popcornjs.org
   *
   * Copyright 2011, Mozilla Foundation
   * Licensed under the MIT license
   */
  
  
  
  (function(){
  
  /**
   * Cross-browser full element.classList implementation for IE9 and friends.
   * 2011-06-15
   *
   * By Eli Grey, http://purl.eligrey.com/github/classList.js/blob/master/classList.js
   * Public Domain.
   * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
   **/
  
  if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
    (function (view) {
      "use strict";
  
      var classListProp = "classList",
          protoProp = "prototype",
          elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
          objCtr = Object,
          strTrim = String[protoProp].trim || function () {
            return this.replace(/^\s+|\s+$/g, "");
          },
          arrIndexOf = Array[protoProp].indexOf || function (item) {
            var i = 0,
                len = this.length;
            for (; i < len; i++) {
              if (i in this && this[i] === item) {
                return i;
              }
            }
            return -1;
          },
          // Vendors: please allow content code to instantiate DOMExceptions
          DOMEx = function (type, message) {
            this.name = type;
            this.code = DOMException[type];
            this.message = message;
          },
          checkTokenAndGetIndex = function (classList, token) {
            if (token === "") {
              throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified");
            }
            if (/\s/.test(token)) {
              throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character");
            }
            return arrIndexOf.call(classList, token);
          },
          ClassList = function (elem) {
            var trimmedClasses = strTrim.call(elem.className),
              classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
              i = 0,
              len = classes.length;
            for (; i < len; i++) {
              this.push(classes[i]);
            }
            this._updateClassName = function () {
              elem.className = this.toString();
            };
          },
          classListProto = ClassList[protoProp] = [],
          classListGetter = function () {
            return new ClassList(this);
          };
  
        // Most DOMException implementations don't allow calling DOMException's toString()
        // on non-DOMExceptions. Error's toString() is sufficient here.
        DOMEx[protoProp] = Error[protoProp];
        classListProto.item = function (i) {
          return this[i] || null;
        };
        classListProto.contains = function (token) {
          token += "";
          return checkTokenAndGetIndex(this, token) !== -1;
        };
        classListProto.add = function (token) {
          token += "";
          if (checkTokenAndGetIndex(this, token) === -1) {
            this.push(token);
            this._updateClassName();
          }
        };
        classListProto.remove = function (token) {
          token += "";
          var index = checkTokenAndGetIndex(this, token);
          if (index !== -1) {
            this.splice(index, 1);
            this._updateClassName();
          }
        };
        classListProto.toggle = function (token) {
          token += "";
          if (checkTokenAndGetIndex(this, token) === -1) {
            this.add(token);
          } else {
            this.remove(token);
          }
        };
        classListProto.toString = function () {
          return this.join(" ");
        };
  
        if (objCtr.defineProperty) {
          var classListPropDesc = {
            get: classListGetter,
            enumerable: true,
            configurable: true
          };
          try {
            objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
          } catch (ex) { // IE 8 doesn't support enumerable:true
            if (ex.number === -0x7FF5EC54) {
              classListPropDesc.enumerable = false;
              objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
            }
          }
        } else if (objCtr[protoProp].__defineGetter__) {
          elemCtrProto.__defineGetter__(classListProp, classListGetter);
        }
    }(self));
  }
  }());
  (function() {
  
    document.addEventListener = document.addEventListener || function( event, callBack ) {
  
      event = ( event === "DOMContentLoaded" ) ? "onreadystatechange" : "on" + event;
  
      document.attachEvent( event, callBack );
    };
  
    document.removeEventListener = document.removeEventListener || function( event, callBack ) {
  
      event = ( event === "DOMContentLoaded" ) ? "onreadystatechange" : "on" + event;
  
      document.detachEvent( event, callBack );
    };
  
    HTMLScriptElement.prototype.addEventListener = HTMLScriptElement.prototype.addEventListener || function( event, callBack ) {
  
      event = ( event === "load" ) ? "onreadystatechange" : "on" + event;
  
      this.attachEvent( event, callBack );
    };
  
    HTMLScriptElement.prototype.removeEventListener = HTMLScriptElement.prototype.removeEventListener || function( event, callBack ) {
  
      event = ( event === "load" ) ? "onreadystatechange" : "on" + event;
  
      this.detachEvent( event, callBack );
    };
  
    document.createEvent = document.createEvent || function ( type ) {
  
      return {
        type : null,
        target : null,
        currentTarget : null,
        cancelable : false,
        bubbles : false,
        initEvent : function (type, bubbles, cancelable)  {
            this.type = type;
        },
        stopPropagation : function () {},
        stopImmediatePropagation : function () {}
      }
    };
  
    Array.prototype.forEach = Array.prototype.forEach || function( fn, context ) {
  
      var obj = this,
          hasOwn = Object.prototype.hasOwnProperty;
  
      if ( !obj || !fn ) {
        return {};
      }
  
      context = context || this;
  
      var key, len;
  
      for ( key in obj ) {
        if ( hasOwn.call( obj, key ) ) {
          fn.call( context, obj[ key ], key, obj );
        }
      }
      return obj;
    };
  
    // Production steps of ECMA-262, Edition 5, 15.4.4.19
    // Reference: http://es5.github.com/#x15.4.4.19
    if ( !Array.prototype.map ) {
  
      Array.prototype.map = function( callback, thisArg ) {
  
        var T, A, k;
  
        if ( this == null ) {
          throw new TypeError( "this is null or not defined" );
        }
  
        // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
        var O = Object( this );
  
        // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
        // 3. Let len be ToUint32(lenValue).
        var len = O.length >>> 0;
  
        // 4. If IsCallable(callback) is false, throw a TypeError exception.
        // See: http://es5.github.com/#x9.11
        if ( {}.toString.call( callback ) != "[object Function]" ) {
          throw new TypeError( callback + " is not a function" );
        }
  
        // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
        if ( thisArg ) {
          T = thisArg;
        }
  
        // 6. Let A be a new array created as if by the expression new Array(len) where Array is
        // the standard built-in constructor with that name and len is the value of len.
        A = new Array( len );
  
        // 7. Let k be 0
        k = 0;
  
        // 8. Repeat, while k < len
        while( k < len ) {
  
          var kValue, mappedValue;
  
          // a. Let Pk be ToString(k).
          //   This is implicit for LHS operands of the in operator
          // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
          //   This step can be combined with c
          // c. If kPresent is true, then
          if ( k in O ) {
  
            // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
            kValue = O[ k ];
  
            // ii. Let mappedValue be the result of calling the Call internal method of callback
            // with T as the this value and argument list containing kValue, k, and O.
            mappedValue = callback.call( T, kValue, k, O );
  
            // iii. Call the DefineOwnProperty internal method of A with arguments
            // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
            // and false.
  
            // In browsers that support Object.defineProperty, use the following:
            // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
  
            // For best browser support, use the following:
            A[ k ] = mappedValue;
          }
          // d. Increase k by 1.
          k++;
        }
  
        // 9. return A
        return A;
      };
    }
  
    if ( !Array.prototype.indexOf ) {
  
      Array.prototype.indexOf = function ( searchElement /*, fromIndex */ ) {
  
        if ( this == null) {
  
          throw new TypeError();
        }
  
        var t = Object( this ),
            len = t.length >>> 0;
  
        if ( len === 0 ) {
  
          return -1;
        }
  
        var n = 0;
  
        if ( arguments.length > 0 ) {
  
          n = Number( arguments[ 1 ] );
  
          if ( n != n ) { // shortcut for verifying if it's NaN
  
            n = 0;
          } else if ( n != 0 && n != Infinity && n != -Infinity ) {
  
            n = ( n > 0 || -1 ) * Math.floor( Math.abs( n ) );
          }
        }
  
        if ( n >= len ) {
          return -1;
        }
  
        var k = n >= 0 ? n : Math.max( len - Math.abs( n ), 0 );
  
        for (; k < len; k++ ) {
  
          if ( k in t && t[ k ] === searchElement ) {
  
            return k;
          }
        }
  
        return -1;
      }
    }
  })();
  
  (function(global, document) {
  
    // Popcorn.js does not support archaic browsers
    if ( !document.addEventListener ) {
      global.Popcorn = {
        isSupported: false
      };
  
      var methods = ( "removeInstance addInstance getInstanceById removeInstanceById " +
            "forEach extend effects error guid sizeOf isArray nop position disable enable destroy" +
            "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " +
            "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/);
  
      while ( methods.length ) {
        global.Popcorn[ methods.shift() ] = function() {};
      }
      return;
    }
  
    var
  
    AP = Array.prototype,
    OP = Object.prototype,
  
    forEach = AP.forEach,
    slice = AP.slice,
    hasOwn = OP.hasOwnProperty,
    toString = OP.toString,
  
    // Copy global Popcorn (may not exist)
    _Popcorn = global.Popcorn,
  
    //  Ready fn cache
    readyStack = [],
    readyBound = false,
    readyFired = false,
  
    //  Non-public internal data object
    internal = {
      events: {
        hash: {},
        apis: {}
      }
    },
  
    //  Non-public `requestAnimFrame`
    //  http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    requestAnimFrame = (function(){
      return global.requestAnimationFrame ||
        global.webkitRequestAnimationFrame ||
        global.mozRequestAnimationFrame ||
        global.oRequestAnimationFrame ||
        global.msRequestAnimationFrame ||
        function( callback, element ) {
          global.setTimeout( callback, 16 );
        };
    }()),
  
    //  Non-public `getKeys`, return an object's keys as an array
    getKeys = function( obj ) {
      return Object.keys ? Object.keys( obj ) : (function( obj ) {
        var item,
            list = [];
  
        for ( item in obj ) {
          if ( hasOwn.call( obj, item ) ) {
            list.push( item );
          }
        }
        return list;
      })( obj );
    },
  
    //  Declare constructor
    //  Returns an instance object.
    Popcorn = function( entity, options ) {
      //  Return new Popcorn object
      return new Popcorn.p.init( entity, options || null );
    };
  
    //  Popcorn API version, automatically inserted via build system.
    Popcorn.version = "@VERSION";
  
    //  Boolean flag allowing a client to determine if Popcorn can be supported
    Popcorn.isSupported = true;
  
    //  Instance caching
    Popcorn.instances = [];
  
    //  Declare a shortcut (Popcorn.p) to and a definition of
    //  the new prototype for our Popcorn constructor
    Popcorn.p = Popcorn.prototype = {
  
      init: function( entity, options ) {
  
        var matches,
            self = this;
  
        //  Supports Popcorn(function () { /../ })
        //  Originally proposed by Daniel Brooks
  
        if ( typeof entity === "function" ) {
  
          //  If document ready has already fired
          if ( document.readyState === "complete" ) {
  
            entity( document, Popcorn );
  
            return;
          }
          //  Add `entity` fn to ready stack
          readyStack.push( entity );
  
          //  This process should happen once per page load
          if ( !readyBound ) {
  
            //  set readyBound flag
            readyBound = true;
  
            var DOMContentLoaded  = function() {
  
              readyFired = true;
  
              //  Remove global DOM ready listener
              document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
  
              //  Execute all ready function in the stack
              for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) {
  
                readyStack[ i ].call( document, Popcorn );
  
              }
              //  GC readyStack
              readyStack = null;
            };
  
            //  Register global DOM ready listener
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
          }
  
          return;
        }
  
        if ( typeof entity === "string" ) {
          try {
            matches = document.querySelector( entity );
          } catch( e ) {
            throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity );
          }
        }
  
        //  Get media element by id or object reference
        this.media = matches || entity;
  
        //  Create an audio or video element property reference
        this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media;
  
        //  Register new instance
        Popcorn.instances.push( this );
  
        this.options = options || {};
  
        this.isDestroyed = false;
  
        this.data = {
  
          // data structure of all
          running: {
            cue: []
          },
  
          // Executed by either timeupdate event or in rAF loop
          timeUpdate: Popcorn.nop,
  
          // Allows disabling a plugin per instance
          disabled: {},
  
          // Stores DOM event queues by type
          events: {},
  
          // Stores Special event hooks data
          hooks: {},
  
          // Store track event history data
          history: [],
  
          // Stores ad-hoc state related data]
          state: {
            volume: this.media.volume
          },
  
          // Store track event object references by trackId
          trackRefs: {},
  
          // Playback track event queues
          trackEvents: {
            byStart: [{
  
              start: -1,
              end: -1
            }],
            byEnd: [{
              start: -1,
              end: -1
            }],
            animating: [],
            startIndex: 0,
            endIndex: 0,
            previousUpdateTime: -1
          }
        };
  
        //  function to fire when video is ready
        var isReady = function() {
  
          // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598
          // it is possible the video's time is less than 0
          // this has the potential to call track events more than once, when they should not
          // start: 0, end: 1 will start, end, start again, when it should just start
          // just setting it to 0 if it is below 0 fixes this issue
          if ( self.media.currentTime < 0 ) {
  
            self.media.currentTime = 0;
          }
  
          self.media.removeEventListener( "loadeddata", isReady, false );
  
          var duration, videoDurationPlus,
              runningPlugins, runningPlugin, rpLength, rpNatives;
  
          //  Adding padding to the front and end of the arrays
          //  this is so we do not fall off either end
          duration = self.media.duration;
  
          //  Check for no duration info (NaN)
          videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
  
          Popcorn.addTrackEvent( self, {
            start: videoDurationPlus,
            end: videoDurationPlus
          });
  
          if ( self.options.frameAnimation ) {
  
            //  if Popcorn is created with frameAnimation option set to true,
            //  requestAnimFrame is used instead of "timeupdate" media event.
            //  This is for greater frame time accuracy, theoretically up to
            //  60 frames per second as opposed to ~4 ( ~every 15-250ms)
            self.data.timeUpdate = function () {
  
              Popcorn.timeUpdate( self, {} );
  
              // fire frame for each enabled active plugin of every type
              Popcorn.forEach( Popcorn.manifest, function( key, val ) {
  
                runningPlugins = self.data.running[ val ];
  
                // ensure there are running plugins on this type on this instance
                if ( runningPlugins ) {
  
                  rpLength = runningPlugins.length;
                  for ( var i = 0; i < rpLength; i++ ) {
  
                    runningPlugin = runningPlugins[ i ];
                    rpNatives = runningPlugin._natives;
                    rpNatives && rpNatives.frame &&
                      rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() );
                  }
                }
              });
  
              self.emit( "timeupdate" );
  
              !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
            };
  
            !self.isDestroyed && requestAnimFrame( self.data.timeUpdate );
  
          } else {
  
            self.data.timeUpdate = function( event ) {
              Popcorn.timeUpdate( self, event );
            };
  
            if ( !self.isDestroyed ) {
              self.media.addEventListener( "timeupdate", self.data.timeUpdate, false );
            }
          }
        };
  
        Object.defineProperty( this, "error", {
          get: function() {
  
            return self.media.error;
          }
        });
  
        if ( self.media.readyState >= 2 ) {
  
          isReady();
        } else {
  
          self.media.addEventListener( "loadeddata", isReady, false );
        }
  
        return this;
      }
    };
  
    //  Extend constructor prototype to instance prototype
    //  Allows chaining methods to instances
    Popcorn.p.init.prototype = Popcorn.p;
  
    Popcorn.forEach = function( obj, fn, context ) {
  
      if ( !obj || !fn ) {
        return {};
      }
  
      context = context || this;
  
      var key, len;
  
      // Use native whenever possible
      if ( forEach && obj.forEach === forEach ) {
        return obj.forEach( fn, context );
      }
  
      if ( toString.call( obj ) === "[object NodeList]" ) {
        for ( key = 0, len = obj.length; key < len; key++ ) {
          fn.call( context, obj[ key ], key, obj );
        }
        return obj;
      }
  
      for ( key in obj ) {
        if ( hasOwn.call( obj, key ) ) {
          fn.call( context, obj[ key ], key, obj );
        }
      }
      return obj;
    };
  
    Popcorn.extend = function( obj ) {
      var dest = obj, src = slice.call( arguments, 1 );
  
      Popcorn.forEach( src, function( copy ) {
        for ( var prop in copy ) {
          dest[ prop ] = copy[ prop ];
        }
      });
  
      return dest;
    };
  
  
    // A Few reusable utils, memoized onto Popcorn
    Popcorn.extend( Popcorn, {
      noConflict: function( deep ) {
  
        if ( deep ) {
          global.Popcorn = _Popcorn;
        }
  
        return Popcorn;
      },
      error: function( msg ) {
        throw new Error( msg );
      },
      guid: function( prefix ) {
        Popcorn.guid.counter++;
        return  ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
      },
      sizeOf: function( obj ) {
        var size = 0;
  
        for ( var prop in obj ) {
          size++;
        }
  
        return size;
      },
      isArray: Array.isArray || function( array ) {
        return toString.call( array ) === "[object Array]";
      },
  
      nop: function() {},
  
      position: function( elem ) {
  
        var clientRect = elem.getBoundingClientRect(),
            bounds = {},
            doc = elem.ownerDocument,
            docElem = document.documentElement,
            body = document.body,
            clientTop, clientLeft, scrollTop, scrollLeft, top, left;
  
        //  Determine correct clientTop/Left
        clientTop = docElem.clientTop || body.clientTop || 0;
        clientLeft = docElem.clientLeft || body.clientLeft || 0;
  
        //  Determine correct scrollTop/Left
        scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop );
        scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft );
  
        //  Temp top/left
        top = Math.ceil( clientRect.top + scrollTop - clientTop );
        left = Math.ceil( clientRect.left + scrollLeft - clientLeft );
  
        for ( var p in clientRect ) {
          bounds[ p ] = Math.round( clientRect[ p ] );
        }
  
        return Popcorn.extend({}, bounds, { top: top, left: left });
      },
  
      disable: function( instance, plugin ) {
  
        if ( !instance.data.disabled[ plugin ] ) {
  
          instance.data.disabled[ plugin ] = true;
  
          for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
  
            event = instance.data.running[ plugin ][ i ];
            event._natives.end.call( instance, null, event  );
          }
        }
  
        return instance;
      },
      enable: function( instance, plugin ) {
  
        if ( instance.data.disabled[ plugin ] ) {
  
          instance.data.disabled[ plugin ] = false;
  
          for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) {
  
            event = instance.data.running[ plugin ][ i ];
            event._natives.start.call( instance, null, event  );
          }
        }
  
        return instance;
      },
      destroy: function( instance ) {
        var events = instance.data.events,
            singleEvent, item, fn, plugin;
  
        //  Iterate through all events and remove them
        for ( item in events ) {
          singleEvent = events[ item ];
          for ( fn in singleEvent ) {
            delete singleEvent[ fn ];
          }
          events[ item ] = null;
        }
  
        // remove all plugins off the given instance
        for ( plugin in Popcorn.registryByName ) {
          Popcorn.removePlugin( instance, plugin );
        }
  
        if ( !instance.isDestroyed ) {
          instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false );
          instance.isDestroyed = true;
        }
      }
    });
  
    //  Memoized GUID Counter
    Popcorn.guid.counter = 1;
  
    //  Factory to implement getters, setters and controllers
    //  as Popcorn instance methods. The IIFE will create and return
    //  an object with defined methods
    Popcorn.extend(Popcorn.p, (function() {
  
        var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " +
                      "autoplay loop controls muted buffered readyState seeking paused played seekable ended",
            ret = {};
  
  
        //  Build methods, store in object that is returned and passed to extend
        Popcorn.forEach( methods.split( /\s+/g ), function( name ) {
  
          ret[ name ] = function( arg ) {
            var previous;
  
            if ( typeof this.media[ name ] === "function" ) {
  
              // Support for shorthanded play(n)/pause(n) jump to currentTime
              // If arg is not null or undefined and called by one of the
              // allowed shorthandable methods, then set the currentTime
              // Supports time as seconds or SMPTE
              if ( arg != null && /play|pause/.test( name ) ) {
                this.media.currentTime = Popcorn.util.toSeconds( arg );
              }
  
              this.media[ name ]();
  
              return this;
            }
  
            if ( arg != null ) {
              // Capture the current value of the attribute property
              previous = this.media[ name ];
  
              // Set the attribute property with the new value
              this.media[ name ] = arg;
  
              // If the new value is not the same as the old value
              // emit an "attrchanged event"
              if ( previous !== arg ) {
                this.emit( "attrchange", {
                  attribute: name,
                  previousValue: previous,
                  currentValue: arg
                });
              }
              return this;
            }
  
            return this.media[ name ];
          };
        });
  
        return ret;
  
      })()
    );
  
    Popcorn.forEach( "enable disable".split(" "), function( method ) {
      Popcorn.p[ method ] = function( plugin ) {
        return Popcorn[ method ]( this, plugin );
      };
    });
  
    Popcorn.extend(Popcorn.p, {
  
      //  Rounded currentTime
      roundTime: function() {
        return -~this.media.currentTime;
      },
  
      //  Attach an event to a single point in time
      exec: function( time, fn ) {
  
        //  Creating a one second track event with an empty end
        Popcorn.addTrackEvent( this, {
          start: time,
          end: time + 1,
          _running: false,
          _natives: {
            start: fn || Popcorn.nop,
            end: Popcorn.nop,
            type: "cue"
          }
        });
  
        return this;
      },
  
      // Mute the calling media, optionally toggle
      mute: function( toggle ) {
  
        var event = toggle == null || toggle === true ? "muted" : "unmuted";
  
        // If `toggle` is explicitly `false`,
        // unmute the media and restore the volume level
        if ( event === "unmuted" ) {
          this.media.muted = false;
          this.media.volume = this.data.state.volume;
        }
  
        // If `toggle` is either null or undefined,
        // save the current volume and mute the media element
        if ( event === "muted" ) {
          this.data.state.volume = this.media.volume;
          this.media.muted = true;
        }
  
        // Trigger either muted|unmuted event
        this.emit( event );
  
        return this;
      },
  
      // Convenience method, unmute the calling media
      unmute: function( toggle ) {
  
        return this.mute( toggle == null ? false : !toggle );
      },
  
      // Get the client bounding box of an instance element
      position: function() {
        return Popcorn.position( this.media );
      },
  
      // Toggle a plugin's playback behaviour (on or off) per instance
      toggle: function( plugin ) {
        return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin );
      },
  
      // Set default values for plugin options objects per instance
      defaults: function( plugin, defaults ) {
  
        // If an array of default configurations is provided,
        // iterate and apply each to this instance
        if ( Popcorn.isArray( plugin ) ) {
  
          Popcorn.forEach( plugin, function( obj ) {
            for ( var name in obj ) {
              this.defaults( name, obj[ name ] );
            }
          }, this );
  
          return this;
        }
  
        if ( !this.options.defaults ) {
          this.options.defaults = {};
        }
  
        if ( !this.options.defaults[ plugin ] ) {
          this.options.defaults[ plugin ] = {};
        }
  
        Popcorn.extend( this.options.defaults[ plugin ], defaults );
  
        return this;
      }
    });
  
    Popcorn.Events  = {
      UIEvents: "blur focus focusin focusout load resize scroll unload",
      MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
      Events: "loadstart progress suspend emptied stalled play pause error " +
              "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
              "seeking seeked timeupdate ended ratechange durationchange volumechange"
    };
  
    Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
                             Popcorn.Events.MouseEvents + " " +
                             Popcorn.Events.Events;
  
    internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ];
  
    // Privately compile events table at load time
    (function( events, data ) {
  
      var apis = internal.events.apiTypes,
      eventsList = events.Natives.split( /\s+/g ),
      idx = 0, len = eventsList.length, prop;
  
      for( ; idx < len; idx++ ) {
        data.hash[ eventsList[idx] ] = true;
      }
  
      apis.forEach(function( val, idx ) {
  
        data.apis[ val ] = {};
  
        var apiEvents = events[ val ].split( /\s+/g ),
        len = apiEvents.length,
        k = 0;
  
        for ( ; k < len; k++ ) {
          data.apis[ val ][ apiEvents[ k ] ] = true;
        }
      });
    })( Popcorn.Events, internal.events );
  
    Popcorn.events = {
  
      isNative: function( type ) {
        return !!internal.events.hash[ type ];
      },
      getInterface: function( type ) {
  
        if ( !Popcorn.events.isNative( type ) ) {
          return false;
        }
  
        var eventApi = internal.events,
          apis = eventApi.apiTypes,
          apihash = eventApi.apis,
          idx = 0, len = apis.length, api, tmp;
  
        for ( ; idx < len; idx++ ) {
          tmp = apis[ idx ];
  
          if ( apihash[ tmp ][ type ] ) {
            api = tmp;
            break;
          }
        }
        return api;
      },
      //  Compile all native events to single array
      all: Popcorn.Events.Natives.split( /\s+/g ),
      //  Defines all Event handling static functions
      fn: {
        trigger: function( type, data ) {
  
          var eventInterface, evt;
          //  setup checks for custom event system
          if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
  
            eventInterface  = Popcorn.events.getInterface( type );
  
            if ( eventInterface ) {
  
              evt = document.createEvent( eventInterface );
              evt.initEvent( type, true, true, global, 1 );
  
              this.media.dispatchEvent( evt );
  
              return this;
            }
  
            //  Custom events
            Popcorn.forEach( this.data.events[ type ], function( obj, key ) {
  
              obj.call( this, data );
  
            }, this );
  
          }
  
          return this;
        },
        listen: function( type, fn ) {
  
          var self = this,
              hasEvents = true,
              eventHook = Popcorn.events.hooks[ type ],
              origType = type,
              tmp;
  
          if ( !this.data.events[ type ] ) {
            this.data.events[ type ] = {};
            hasEvents = false;
          }
  
          // Check and setup event hooks
          if ( eventHook ) {
  
            // Execute hook add method if defined
            if ( eventHook.add ) {
              eventHook.add.call( this, {}, fn );
            }
  
            // Reassign event type to our piggyback event type if defined
            if ( eventHook.bind ) {
              type = eventHook.bind;
            }
  
            // Reassign handler if defined
            if ( eventHook.handler ) {
              tmp = fn;
  
              fn = function wrapper( event ) {
                eventHook.handler.call( self, event, tmp );
              };
            }
  
            // assume the piggy back event is registered
            hasEvents = true;
  
            // Setup event registry entry
            if ( !this.data.events[ type ] ) {
              this.data.events[ type ] = {};
              // Toggle if the previous assumption was untrue
              hasEvents = false;
            }
          }
  
          //  Register event and handler
          this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
  
          // only attach one event of any type
          if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
  
            this.media.addEventListener( type, function( event ) {
  
              Popcorn.forEach( self.data.events[ type ], function( obj, key ) {
                if ( typeof obj === "function" ) {
                  obj.call( self, event );
                }
              });
  
            }, false);
          }
          return this;
        },
        unlisten: function( type, fn ) {
  
          if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) {
  
            delete this.data.events[ type ][ fn ];
  
            return this;
          }
  
          this.data.events[ type ] = null;
  
          return this;
        }
      },
      hooks: {
        canplayall: {
          bind: "canplaythrough",
          add: function( event, callback ) {
  
            var state = false;
  
            if ( this.media.readyState ) {
  
              callback.call( this, event );
  
              state = true;
            }
  
            this.data.hooks.canplayall = {
              fired: state
            };
          },
          // declare special handling instructions
          handler: function canplayall( event, callback ) {
  
            if ( !this.data.hooks.canplayall.fired ) {
              // trigger original user callback once
              callback.call( this, event );
  
              this.data.hooks.canplayall.fired = true;
            }
          }
        }
      }
    };
  
    //  Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
    //  Extend aliases (on, off, emit)
    Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) {
      Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ];
    });
  
    // Internal Only - Adds track events to the instance object
    Popcorn.addTrackEvent = function( obj, track ) {
  
      // Determine if this track has default options set for it
      // If so, apply them to the track object
      if ( track && track._natives && track._natives.type &&
          ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) {
  
        track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track );
      }
  
      if ( track._natives ) {
        //  Supports user defined track event id
        track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
  
        //  Push track event ids into the history
        obj.data.history.push( track._id );
      }
  
      track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate );
      track.end   = Popcorn.util.toSeconds( track.end, obj.options.framerate );
  
      //  Store this definition in an array sorted by times
      var byStart = obj.data.trackEvents.byStart,
          byEnd = obj.data.trackEvents.byEnd,
          startIndex, endIndex;
  
      for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) {
  
        if ( track.start >= byStart[ startIndex ].start ) {
          byStart.splice( startIndex + 1, 0, track );
          break;
        }
      }
  
      for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) {
  
        if ( track.end > byEnd[ endIndex ].end ) {
          byEnd.splice( endIndex + 1, 0, track );
          break;
        }
      }
  
      // Display track event immediately if it's enabled and current
      if ( track.end > obj.media.currentTime &&
          track.start <= obj.media.currentTime ) {
  
        track._running = true;
        obj.data.running[ track._natives.type ].push( track );
  
        if ( !obj.data.disabled[ track._natives.type ] ) {
  
          track._natives.start.call( obj, null, track );
        }
      }
  
      // update startIndex and endIndex
      if ( startIndex <= obj.data.trackEvents.startIndex &&
        track.start <= obj.data.trackEvents.previousUpdateTime ) {
  
        obj.data.trackEvents.startIndex++;
      }
  
      if ( endIndex <= obj.data.trackEvents.endIndex &&
        track.end < obj.data.trackEvents.previousUpdateTime ) {
  
        obj.data.trackEvents.endIndex++;
      }
  
      this.timeUpdate( obj, null, true );
  
      // Store references to user added trackevents in ref table
      if ( track._id ) {
        Popcorn.addTrackEvent.ref( obj, track );
      }
    };
  
    // Internal Only - Adds track event references to the instance object's trackRefs hash table
    Popcorn.addTrackEvent.ref = function( obj, track ) {
      obj.data.trackRefs[ track._id ] = track;
  
      return obj;
    };
  
    Popcorn.removeTrackEvent  = function( obj, removeId ) {
  
      var start, end, animate,
          historyLen = obj.data.history.length,
          length = obj.data.trackEvents.byStart.length,
          index = 0,
          indexWasAt = 0,
          byStart = [],
          byEnd = [],
          animating = [],
          history = [];
  
      while ( --length > -1 ) {
        start = obj.data.trackEvents.byStart[ index ];
        end = obj.data.trackEvents.byEnd[ index ];
  
        // Padding events will not have _id properties.
        // These should be safely pushed onto the front and back of the
        // track event array
        if ( !start._id ) {
          byStart.push( start );
          byEnd.push( end );
        }
  
        // Filter for user track events (vs system track events)
        if ( start._id ) {
  
          // If not a matching start event for removal
          if ( start._id !== removeId ) {
            byStart.push( start );
          }
  
          // If not a matching end event for removal
          if ( end._id !== removeId ) {
            byEnd.push( end );
          }
  
          // If the _id is matched, capture the current index
          if ( start._id === removeId ) {
            indexWasAt = index;
  
            // If a _teardown function was defined,
            // enforce for track event removals
            if ( start._natives._teardown ) {
              start._natives._teardown.call( obj, start );
            }
          }
        }
        // Increment the track index
        index++;
      }
  
      // Reset length to be used by the condition below to determine
      // if animating track events should also be filtered for removal.
      // Reset index below to be used by the reverse while as an
      // incrementing counter
      length = obj.data.trackEvents.animating.length;
      index = 0;
  
      if ( length ) {
        while ( --length > -1 ) {
          animate = obj.data.trackEvents.animating[ index ];
  
          // Padding events will not have _id properties.
          // These should be safely pushed onto the front and back of the
          // track event array
          if ( !animate._id ) {
            animating.push( animate );
          }
  
          // If not a matching animate event for removal
          if ( animate._id && animate._id !== removeId ) {
            animating.push( animate );
          }
          // Increment the track index
          index++;
        }
      }
  
      //  Update
      if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
        obj.data.trackEvents.startIndex--;
      }
  
      if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
        obj.data.trackEvents.endIndex--;
      }
  
      obj.data.trackEvents.byStart = byStart;
      obj.data.trackEvents.byEnd = byEnd;
      obj.data.trackEvents.animating = animating;
  
      for ( var i = 0; i < historyLen; i++ ) {
        if ( obj.data.history[ i ] !== removeId ) {
          history.push( obj.data.history[ i ] );
        }
      }
  
      // Update ordered history array
      obj.data.history = history;
  
      // Update track event references
      Popcorn.removeTrackEvent.ref( obj, removeId );
    };
  
    // Internal Only - Removes track event references from instance object's trackRefs hash table
    Popcorn.removeTrackEvent.ref = function( obj, removeId ) {
      delete obj.data.trackRefs[ removeId ];
  
      return obj;
    };
  
    // Return an array of track events bound to this instance object
    Popcorn.getTrackEvents = function( obj ) {
  
      var trackevents = [],
        refs = obj.data.trackEvents.byStart,
        length = refs.length,
        idx = 0,
        ref;
  
      for ( ; idx < length; idx++ ) {
        ref = refs[ idx ];
        // Return only user attributed track event references
        if ( ref._id ) {
          trackevents.push( ref );
        }
      }
  
      return trackevents;
    };
  
    // Internal Only - Returns an instance object's trackRefs hash table
    Popcorn.getTrackEvents.ref = function( obj ) {
      return obj.data.trackRefs;
    };
  
    // Return a single track event bound to this instance object
    Popcorn.getTrackEvent = function( obj, trackId ) {
      return obj.data.trackRefs[ trackId ];
    };
  
    // Internal Only - Returns an instance object's track reference by track id
    Popcorn.getTrackEvent.ref = function( obj, trackId ) {
      return obj.data.trackRefs[ trackId ];
    };
  
    Popcorn.getLastTrackEventId = function( obj ) {
      return obj.data.history[ obj.data.history.length - 1 ];
    };
  
    Popcorn.timeUpdate = function( obj, event ) {
  
      var currentTime = obj.media.currentTime,
          previousTime = obj.data.trackEvents.previousUpdateTime,
          tracks = obj.data.trackEvents,
          end = tracks.endIndex,
          start = tracks.startIndex,
          byStartLen = tracks.byStart.length,
          byEndLen = tracks.byEnd.length,
          registryByName = Popcorn.registryByName,
          trackstart = "trackstart",
          trackend = "trackend",
  
          byEnd, byStart, byAnimate, natives, type, runningPlugins;
  
      //  Playbar advancing
      if ( previousTime <= currentTime ) {
  
        while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) {
  
          byEnd = tracks.byEnd[ end ];
          natives = byEnd._natives;
          type = natives && natives.type;
  
          //  If plugin does not exist on this instance, remove it
          if ( !natives ||
              ( !!registryByName[ type ] ||
                !!obj[ type ] ) ) {
  
            if ( byEnd._running === true ) {
  
              byEnd._running = false;
              runningPlugins = obj.data.running[ type ];
              runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 );
  
              if ( !obj.data.disabled[ type ] ) {
  
                natives.end.call( obj, event, byEnd );
  
                obj.emit( trackend,
                  Popcorn.extend({}, byEnd, {
                    plugin: type,
                    type: trackend
                  })
                );
              }
            }
  
            end++;
          } else {
            // remove track event
            Popcorn.removeTrackEvent( obj, byEnd._id );
            return;
          }
        }
  
        while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) {
  
          byStart = tracks.byStart[ start ];
          natives = byStart._natives;
          type = natives && natives.type;
  
          //  If plugin does not exist on this instance, remove it
          if ( !natives ||
              ( !!registryByName[ type ] ||
                !!obj[ type ] ) ) {
  
            if ( byStart.end > currentTime &&
                  byStart._running === false ) {
  
              byStart._running = true;
              obj.data.running[ type ].push( byStart );
  
              if ( !obj.data.disabled[ type ] ) {
  
                natives.start.call( obj, event, byStart );
  
                obj.emit( trackstart,
                  Popcorn.extend({}, byStart, {
                    plugin: type,
                    type: trackstart
                  })
                );
              }
            }
            start++;
          } else {
            // remove track event
            Popcorn.removeTrackEvent( obj, byStart._id );
            return;
          }
        }
  
      // Playbar receding
      } else if ( previousTime > currentTime ) {
  
        while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) {
  
          byStart = tracks.byStart[ start ];
          natives = byStart._natives;
          type = natives && natives.type;
  
          // if plugin does not exist on this instance, remove it
          if ( !natives ||
              ( !!registryByName[ type ] ||
                !!obj[ type ] ) ) {
  
            if ( byStart._running === true ) {
  
              byStart._running = false;
              runningPlugins = obj.data.running[ type ];
              runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 );
  
              if ( !obj.data.disabled[ type ] ) {
  
                natives.end.call( obj, event, byStart );
  
                obj.emit( trackend,
                  Popcorn.extend({}, byStart, {
                    plugin: type,
                    type: trackend
                  })
                );
              }
            }
            start--;
          } else {
            // remove track event
            Popcorn.removeTrackEvent( obj, byStart._id );
            return;
          }
        }
  
        while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) {
  
          byEnd = tracks.byEnd[ end ];
          natives = byEnd._natives;
          type = natives && natives.type;
  
          // if plugin does not exist on this instance, remove it
          if ( !natives ||
              ( !!registryByName[ type ] ||
                !!obj[ type ] ) ) {
  
            if ( byEnd.start <= currentTime &&
                  byEnd._running === false ) {
  
              byEnd._running = true;
              obj.data.running[ type ].push( byEnd );
  
              if ( !obj.data.disabled[ type ] ) {
  
                natives.start.call( obj, event, byEnd );
  
                obj.emit( trackstart,
                  Popcorn.extend({}, byEnd, {
                    plugin: type,
                    type: trackstart
                  })
                );
              }
            }
            end--;
          } else {
            // remove track event
            Popcorn.removeTrackEvent( obj, byEnd._id );
            return;
          }
        }
      }
  
      tracks.endIndex = end;
      tracks.startIndex = start;
      tracks.previousUpdateTime = currentTime;
  
      //enforce index integrity if trackRemoved
      tracks.byStart.length < byStartLen && tracks.startIndex--;
      tracks.byEnd.length < byEndLen && tracks.endIndex--;
  
    };
  
    //  Map and Extend TrackEvent functions to all Popcorn instances
    Popcorn.extend( Popcorn.p, {
  
      getTrackEvents: function() {
        return Popcorn.getTrackEvents.call( null, this );
      },
  
      getTrackEvent: function( id ) {
        return Popcorn.getTrackEvent.call( null, this, id );
      },
  
      getLastTrackEventId: function() {
        return Popcorn.getLastTrackEventId.call( null, this );
      },
  
      removeTrackEvent: function( id ) {
  
        Popcorn.removeTrackEvent.call( null, this, id );
        return this;
      },
  
      removePlugin: function( name ) {
        Popcorn.removePlugin.call( null, this, name );
        return this;
      },
  
      timeUpdate: function( event ) {
        Popcorn.timeUpdate.call( null, this, event );
        return this;
      },
  
      destroy: function() {
        Popcorn.destroy.call( null, this );
        return this;
      }
    });
  
    //  Plugin manifests
    Popcorn.manifest = {};
    //  Plugins are registered
    Popcorn.registry = [];
    Popcorn.registryByName = {};
    //  An interface for extending Popcorn
    //  with plugin functionality
    Popcorn.plugin = function( name, definition, manifest ) {
  
      if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
        Popcorn.error( "'" + name + "' is a protected function name" );
        return;
      }
  
      //  Provides some sugar, but ultimately extends
      //  the definition into Popcorn.p
      var reserved = [ "start", "end" ],
          plugin = {},
          setup,
          isfn = typeof definition === "function",
          methods = [ "_setup", "_teardown", "start", "end", "frame" ];
  
      // combines calls of two function calls into one
      var combineFn = function( first, second ) {
  
        first = first || Popcorn.nop;
        second = second || Popcorn.nop;
  
        return function() {
          first.apply( this, arguments );
          second.apply( this, arguments );
        };
      };
  
      //  If `manifest` arg is undefined, check for manifest within the `definition` object
      //  If no `definition.manifest`, an empty object is a sufficient fallback
      Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  
      // apply safe, and empty default functions
      methods.forEach(function( method ) {
        definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name );
      });
  
      var pluginFn = function( setup, options ) {
  
        if ( !options ) {
          return this;
        }
  
        // When the "ranges" property is set and its value is an array, short-circuit
        // the pluginFn definition to recall itself with an options object generated from
        // each range object in the ranges array. (eg. { start: 15, end: 16 } )
        if ( options.ranges && Popcorn.isArray(options.ranges) ) {
          Popcorn.forEach( options.ranges, function( range ) {
            // Create a fresh object, extend with current options
            // and start/end range object's properties
            // Works with in/out as well.
            var opts = Popcorn.extend( {}, options, range );
  
            // Remove the ranges property to prevent infinitely
            // entering this condition
            delete opts.ranges;
  
            // Call the plugin with the newly created opts object
            this[ name ]( opts );
          }, this);
  
          // Return the Popcorn instance to avoid creating an empty track event
          return this;
        }
  
        //  Storing the plugin natives
        var natives = options._natives = {},
            compose = "",
            originalOpts, manifestOpts;
  
        Popcorn.extend( natives, setup );
  
        options._natives.type = name;
        options._running = false;
  
        natives.start = natives.start || natives[ "in" ];
        natives.end = natives.end || natives[ "out" ];
  
        if ( options.once ) {
          natives.end = combineFn( natives.end, function() {
            this.removeTrackEvent( options._id );
          });
        }
  
        // extend teardown to always call end if running
        natives._teardown = combineFn(function() {
  
          var args = slice.call( arguments ),
              runningPlugins = this.data.running[ natives.type ];
  
          // end function signature is not the same as teardown,
          // put null on the front of arguments for the event parameter
          args.unshift( null );
  
          // only call end if event is running
          args[ 1 ]._running &&
            runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) &&
            natives.end.apply( this, args );
        }, natives._teardown );
  
        // default to an empty string if no effect exists
        // split string into an array of effects
        options.compose = options.compose && options.compose.split( " " ) || [];
        options.effect = options.effect && options.effect.split( " " ) || [];
  
        // join the two arrays together
        options.compose = options.compose.concat( options.effect );
  
        options.compose.forEach(function( composeOption ) {
  
          // if the requested compose is garbage, throw it away
          compose = Popcorn.compositions[ composeOption ] || {};
  
          // extends previous functions with compose function
          methods.forEach(function( method ) {
            natives[ method ] = combineFn( natives[ method ], compose[ method ] );
          });
        });
  
        //  Ensure a manifest object, an empty object is a sufficient fallback
        options._natives.manifest = manifest;
  
        //  Checks for expected properties
        if ( !( "start" in options ) ) {
          options.start = options[ "in" ] || 0;
        }
  
        if ( !options.end && options.end !== 0 ) {
          options.end = options[ "out" ] || Number.MAX_VALUE;
        }
  
        // Use hasOwn to detect non-inherited toString, since all
        // objects will receive a toString - its otherwise undetectable
        if ( !hasOwn.call( options, "toString" ) ) {
          options.toString = function() {
            var props = [
              "start: " + options.start,
              "end: " + options.end,
              "id: " + (options.id || options._id)
            ];
  
            // Matches null and undefined, allows: false, 0, "" and truthy
            if ( options.target != null ) {
              props.push( "target: " + options.target );
            }
  
            return name + " ( " + props.join(", ") + " )";
          };
        }
  
        // Resolves 239, 241, 242
        if ( !options.target ) {
  
          //  Sometimes the manifest may be missing entirely
          //  or it has an options object that doesn't have a `target` property
          manifestOpts = "options" in manifest && manifest.options;
  
          options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target;
        }
  
        // Trigger _setup method if exists
        options._natives._setup && options._natives._setup.call( this, options );
  
        // Create new track event for this instance
        Popcorn.addTrackEvent( this, Popcorn.extend( options, options ) );
  
        //  Future support for plugin event definitions
        //  for all of the native events
        Popcorn.forEach( setup, function( callback, type ) {
  
          if ( type !== "type" ) {
  
            if ( reserved.indexOf( type ) === -1 ) {
  
              this.on( type, callback );
            }
          }
  
        }, this );
  
        return this;
      };
  
      //  Extend Popcorn.p with new named definition
      //  Assign new named definition
      Popcorn.p[ name ] = plugin[ name ] = function( options ) {
  
        this.data.running[ name ] = this.data.running[ name ] || [];
  
        // Merge with defaults if they exist, make sure per call is prioritized
        var defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {},
            mergedSetupOpts = Popcorn.extend( {}, defaults, options );
  
        return pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition,
                                    mergedSetupOpts );
      };
  
      // if the manifest parameter exists we should extend it onto the definition object
      // so that it shows up when calling Popcorn.registry and Popcorn.registryByName
      if ( manifest ) {
        Popcorn.extend( definition, {
          manifest: manifest
        });
      }
  
      //  Push into the registry
      var entry = {
        fn: plugin[ name ],
        definition: definition,
        base: definition,
        parents: [],
        name: name
      };
      Popcorn.registry.push(
         Popcorn.extend( plugin, entry, {
          type: name
        })
      );
      Popcorn.registryByName[ name ] = entry;
  
      return plugin;
    };
  
    // Storage for plugin function errors
    Popcorn.plugin.errors = [];
  
    // Returns wrapped plugin function
    function safeTry( fn, pluginName ) {
      return function() {
  
        //  When Popcorn.plugin.debug is true, do not suppress errors
        if ( Popcorn.plugin.debug ) {
          return fn.apply( this, arguments );
        }
  
        try {
          return fn.apply( this, arguments );
        } catch ( ex ) {
  
          // Push plugin function errors into logging queue
          Popcorn.plugin.errors.push({
            plugin: pluginName,
            thrown: ex,
            source: fn.toString()
          });
  
          // Trigger an error that the instance can listen for
          // and react to
          this.emit( "pluginerror", Popcorn.plugin.errors );
        }
      };
    }
  
    // Debug-mode flag for plugin development
    // True for Popcorn development versions, false for stable/tagged versions
    Popcorn.plugin.debug = ( Popcorn.version === "@" + "VERSION" );
  
    //  removePlugin( type ) removes all tracks of that from all instances of popcorn
    //  removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
    Popcorn.removePlugin = function( obj, name ) {
  
      //  Check if we are removing plugin from an instance or from all of Popcorn
      if ( !name ) {
  
        //  Fix the order
        name = obj;
        obj = Popcorn.p;
  
        if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
          Popcorn.error( "'" + name + "' is a protected function name" );
          return;
        }
  
        var registryLen = Popcorn.registry.length,
            registryIdx;
  
        // remove plugin reference from registry
        for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
          if ( Popcorn.registry[ registryIdx ].name === name ) {
            Popcorn.registry.splice( registryIdx, 1 );
            delete Popcorn.registryByName[ name ];
            delete Popcorn.manifest[ name ];
  
            // delete the plugin
            delete obj[ name ];
  
            // plugin found and removed, stop checking, we are done
            return;
          }
        }
  
      }
  
      var byStart = obj.data.trackEvents.byStart,
          byEnd = obj.data.trackEvents.byEnd,
          animating = obj.data.trackEvents.animating,
          idx, sl;
  
      // remove all trackEvents
      for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
  
        if ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) {
  
          byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] );
  
          byStart.splice( idx, 1 );
  
          // update for loop if something removed, but keep checking
          idx--; sl--;
          if ( obj.data.trackEvents.startIndex <= idx ) {
            obj.data.trackEvents.startIndex--;
            obj.data.trackEvents.endIndex--;
          }
        }
  
        // clean any remaining references in the end index
        // we do this seperate from the above check because they might not be in the same order
        if ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) {
  
          byEnd.splice( idx, 1 );
        }
      }
  
      //remove all animating events
      for ( idx = 0, sl = animating.length; idx < sl; idx++ ) {
  
        if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) {
  
          animating.splice( idx, 1 );
  
          // update for loop if something removed, but keep checking
          idx--; sl--;
        }
      }
  
    };
  
    Popcorn.compositions = {};
  
    //  Plugin inheritance
    Popcorn.compose = function( name, definition, manifest ) {
  
      //  If `manifest` arg is undefined, check for manifest within the `definition` object
      //  If no `definition.manifest`, an empty object is a sufficient fallback
      Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {};
  
      // register the effect by name
      Popcorn.compositions[ name ] = definition;
    };
  
    Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose;
  
    var rnaiveExpr = /^(?:\.|#|\[)/;
  
    //  Basic DOM utilities and helpers API. See #1037
    Popcorn.dom = {
      debug: false,
      //  Popcorn.dom.find( selector, context )
      //
      //  Returns the first element that matches the specified selector
      //  Optionally provide a context element, defaults to `document`
      //
      //  eg.
      //  Popcorn.dom.find("video") returns the first video element
      //  Popcorn.dom.find("#foo") returns the first element with `id="foo"`
      //  Popcorn.dom.find("foo") returns the first element with `id="foo"`
      //     Note: Popcorn.dom.find("foo") is the only allowed deviation
      //           from valid querySelector selector syntax
      //
      //  Popcorn.dom.find(".baz") returns the first element with `class="baz"`
      //  Popcorn.dom.find("[preload]") returns the first element with `preload="..."`
      //  ...
      //  See https://developer.mozilla.org/En/DOM/Document.querySelector
      //
      //
      find: function( selector, context ) {
        var node = null;
  
        //  Trim leading/trailing whitespace to avoid false negatives
        selector = selector.trim();
  
        //  Default context is the `document`
        context = context || document;
  
        if ( selector ) {
          //  If the selector does not begin with "#", "." or "[",
          //  it could be either a nodeName or ID w/o "#"
          if ( !rnaiveExpr.test( selector ) ) {
  
            //  Try finding an element that matches by ID first
            node = document.getElementById( selector );
  
            //  If a match was found by ID, return the element
            if ( node !== null ) {
              return node;
            }
          }
          //  Assume no elements have been found yet
          //  Catch any invalid selector syntax errors and bury them.
          try {
            node = context.querySelector( selector );
          } catch ( e ) {
            if ( Popcorn.dom.debug ) {
              throw new Error(e);
            }
          }
        }
        return node;
      }
    };
  
    //  Cache references to reused RegExps
    var rparams = /\?/,
    //  XHR Setup object
    setup = {
      url: "",
      data: "",
      dataType: "",
      success: Popcorn.nop,
      type: "GET",
      async: true,
      xhr: function() {
        return new global.XMLHttpRequest();
      }
    };
  
    Popcorn.xhr = function( options ) {
  
      options.dataType = options.dataType && options.dataType.toLowerCase() || null;
  
      if ( options.dataType &&
           ( options.dataType === "jsonp" || options.dataType === "script" ) ) {
  
        Popcorn.xhr.getJSONP(
          options.url,
          options.success,
          options.dataType === "script"
        );
        return;
      }
  
      var settings = Popcorn.extend( {}, setup, options );
  
      //  Create new XMLHttpRequest object
      settings.ajax  = settings.xhr();
  
      if ( settings.ajax ) {
  
        if ( settings.type === "GET" && settings.data ) {
  
          //  append query string
          settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
  
          //  Garbage collect and reset settings.data
          settings.data = null;
        }
  
  
        settings.ajax.open( settings.type, settings.url, settings.async );
        settings.ajax.send( settings.data || null );
  
        return Popcorn.xhr.httpData( settings );
      }
    };
  
  
    Popcorn.xhr.httpData = function( settings ) {
  
      var data, json = null,
          parser, xml = null;
  
      settings.ajax.onreadystatechange = function() {
  
        if ( settings.ajax.readyState === 4 ) {
  
          try {
            json = JSON.parse( settings.ajax.responseText );
          } catch( e ) {
            //suppress
          }
  
          data = {
            xml: settings.ajax.responseXML,
            text: settings.ajax.responseText,
            json: json
          };
  
          // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml
          if ( !data.xml || !data.xml.documentElement ) {
            data.xml = null;
  
            try {
              parser = new DOMParser();
              xml = parser.parseFromString( settings.ajax.responseText, "text/xml" );
  
              if ( !xml.getElementsByTagName( "parsererror" ).length ) {
                data.xml = xml;
              }
            } catch ( e ) {
              // data.xml remains null
            }
          }
  
          //  If a dataType was specified, return that type of data
          if ( settings.dataType ) {
            data = data[ settings.dataType ];
          }
  
  
          settings.success.call( settings.ajax, data );
  
        }
      };
      return data;
    };
  
    Popcorn.xhr.getJSONP = function( url, success, isScript ) {
  
      var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement,
        script = document.createElement( "script" ),
        paramStr = url.split( "?" )[ 1 ],
        isFired = false,
        params = [],
        callback, parts, callparam;
  
      if ( paramStr && !isScript ) {
        params = paramStr.split( "&" );
      }
  
      if ( params.length ) {
        parts = params[ params.length - 1 ].split( "=" );
      }
  
      callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ]  ) : "jsonp";
  
      if ( !paramStr && !isScript ) {
        url += "?callback=" + callback;
      }
  
      if ( callback && !isScript ) {
  
        //  If a callback name already exists
        if ( !!window[ callback ] ) {
          //  Create a new unique callback name
          callback = Popcorn.guid( callback );
        }
  
        //  Define the JSONP success callback globally
        window[ callback ] = function( data ) {
          // Fire success callbacks
          success && success( data );
          isFired = true;
        };
  
        //  Replace callback param and callback name
        url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback );
      }
  
      script.addEventListener( "load",  function() {
  
        //  Handling remote script loading callbacks
        if ( isScript ) {
          //  getScript
          success && success();
        }
  
        //  Executing for JSONP requests
        if ( isFired ) {
          //  Garbage collect the callback
          delete window[ callback ];
        }
        //  Garbage collect the script resource
        head.removeChild( script );
      }, false );
  
      script.src = url;
  
      head.insertBefore( script, head.firstChild );
  
      return;
    };
  
    Popcorn.getJSONP = Popcorn.xhr.getJSONP;
  
    Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
  
      return Popcorn.xhr.getJSONP( url, success, true );
    };
  
    Popcorn.util = {
      // Simple function to parse a timestamp into seconds
      // Acceptable formats are:
      // HH:MM:SS.MMM
      // HH:MM:SS;FF
      // Hours and minutes are optional. They default to 0
      toSeconds: function( timeStr, framerate ) {
        // Hours and minutes are optional
        // Seconds must be specified
        // Seconds can be followed by milliseconds OR by the frame information
        var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,
            errorMessage = "Invalid time format",
            digitPairs, lastIndex, lastPair, firstPair,
            frameInfo, frameTime;
  
        if ( typeof timeStr === "number" ) {
          return timeStr;
        }
  
        if ( typeof timeStr === "string" &&
              !validTimeFormat.test( timeStr ) ) {
          Popcorn.error( errorMessage );
        }
  
        digitPairs = timeStr.split( ":" );
        lastIndex = digitPairs.length - 1;
        lastPair = digitPairs[ lastIndex ];
  
        // Fix last element:
        if ( lastPair.indexOf( ";" ) > -1 ) {
  
          frameInfo = lastPair.split( ";" );
          frameTime = 0;
  
          if ( framerate && ( typeof framerate === "number" ) ) {
            frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate;
          }
  
          digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime;
        }
  
        firstPair = digitPairs[ 0 ];
  
        return {
  
          1: parseFloat( firstPair, 10 ),
  
          2: ( parseInt( firstPair, 10 ) * 60 ) +
                parseFloat( digitPairs[ 1 ], 10 ),
  
          3: ( parseInt( firstPair, 10 ) * 3600 ) +
              ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) +
                parseFloat( digitPairs[ 2 ], 10 )
  
        }[ digitPairs.length || 1 ];
      }
    };
  
    // alias for exec function
    Popcorn.p.cue = Popcorn.p.exec;
  
    //  Protected API methods
    Popcorn.protect = {
      natives: getKeys( Popcorn.p ).map(function( val ) {
        return val.toLowerCase();
      })
    };
  
    // Setup logging for deprecated methods
    Popcorn.forEach({
      // Deprecated: Recommended
      "listen": "on",
      "unlisten": "off",
      "trigger": "emit",
      "exec": "cue"
  
    }, function( recommend, api ) {
      var original = Popcorn.p[ api ];
      // Override the deprecated api method with a method of the same name
      // that logs a warning and defers to the new recommended method
      Popcorn.p[ api ] = function() {
        if ( typeof console !== "undefined" && console.warn ) {
          console.warn(
            "Deprecated method '" + api + "', " +
            (recommend == null ? "do not use." : "use '" + recommend + "' instead." )
          );
  
          // Restore api after first warning
          Popcorn.p[ api ] = original;
        }
        return Popcorn.p[ recommend ].apply( this, [].slice.call( arguments ) );
      };
    });
  
  
    //  Exposes Popcorn to global context
    global.Popcorn = Popcorn;
  
  })(window, window.document);
  
  // PLUGIN: Attribution
  
  (function( Popcorn ) {
  
    /**
     * Attribution popcorn plug-in
     * Adds text to an element on the page.
     * Options parameter will need a mandatory start, end, target.
     * Optional parameters include nameofwork, NameOfWorkUrl, CopyrightHolder, CopyrightHolderUrl, license & licenseUrl.
     * Start is the time that you want this plug-in to execute
     * End is the time that you want this plug-in to stop executing
     * Target is the id of the document element that the text needs to be attached to, this target element must exist on the DOM
     * nameofwork is the title of the attribution
     * NameOfWorkUrl is a url that provides more details about the attribution
     * CopyrightHolder is the name of the person/institution that holds the rights to the attribution
     * CopyrightHolderUrl is the url that provides more details about the copyrightholder
     * license is the type of license that the work is copyrighted under
     * LicenseUrl is the url that provides more details about the ticense type
     * @param {Object} options
     *
     * Example:
       var p = Popcorn('#video')
          .attribution({
            start: 5, // seconds
            end: 15, // seconds
            target: 'attributiondiv'
          } )
     *
     */
    Popcorn.plugin( "attribution" , (function() {
  
      var
      common = "data:image/png;base64,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",
      licenses = {
        "cc-by": common + "eeSURBVHja7JpfbNvGHce/R9JBU9Qa89QN2gD5TepLmGTJYyyte9mypiSC7aXrIj8NqDFI6lavLezISpwuE5LJwpACw7aaWJ8L0/kD7B8iyi2wRXYiGikgvUkPNbY+ybXbh5l/bg8kT6RlO7Zjq2maM0488e4o8sPv/e53vzOhlEYIIZ/hadr3RCklBAAFgNt/vwWO48BxHHieB8fx4DkOHO8dOQ6EcOAIASEEIMS/CigoqEPhUAeO42bbtt2jY8O2HTiOzeoc6rD2lFL/Zlj5SUg/fvknAAACgPpweZ53M8d3yzzv1nG8B5mAEC7I14PjgXVcmLbt5WDZDkN2HIeBDYJ+kiALAMJweQFC6Ojmm3O3UKlUUKvVsLa6FrrQYGQQp06dQup7Kbx09kewHR4cZ7kvxOZAQLx3GRg+DnVHArwxRPYH7v2FOrQPNDQajdD5RCIB+ZyM4yeP9RUyAUD/duevEASBQRUEwc28gKo+j+KVIpaXl3d0wWg0irG3xjA8fBqWbcO2LViWl20LlmUzhW+m5L2q+L//+RTXy9fRbDQBAMlkEpIkAQAMw4Cu6wCAeCKO0cwovvmt5/uiYAKA/rP6Dwi80AUrDGBAEJCfmIQ2q7EOoihClmXEYjEMDw8DAKrVKtrtNjRNw8rKCmsrKzJ+NfZLHH72MCzLgmlZsCwTlmWFTYYP2PFs+R5s8eernyMzmsXq6ipkWUapVEIsFgu1abfbyOVy0DQNkUgEl4uXDxwyA3znwzsY8MEOCBgQBkJwRVFENptFJpOBKIpbXlBVVeRyOQY6nojjT+/9Ec8cPgzLMmGaJlPyppDp3gBPvHkBzUYT6XQaMzMz3eHpmaDg9VRVxcjICOKJOC5duXjggDkA4D0bLPA8BD6sXEmSUK/Xkc/nt4ULAOl0Gq1Wiw3NZqOJq8VrIVvOMY+EdLP3txHMTm1us9GELMsYe+ONh7ZPp9OQZRnNRhP3F+oHbiY4AOB8t4znUdXnQ3ArlUrPcNsuiaKISqXCIGuzGqrVefC8sDlkznf7EIK806R94N5rqVRC4oUXNvqhm46GUqkU6nvggF0FuyouXikyUDMzMw9V7XaQ/b7F3xQ9X9qDSzyfmvM8DIIuZLI7yI1GA8lkskcEIyMjbISMjIyE6mKxGJLJZI+ncXAK9h7+5twt5i1ks1mmwr0kURSZUpaXl3Hzxi22YHEhb20idps2u09VVTctb9fnwAD7aqpUKgxOJpNhjXRdh6IoSKVSSKVSKBQKW9ZNT0+H7J2v4sqdSkC9XdNAyKOZiMc9uQsNQsARglqt5rpYsszA6LqOVCoV6qTrOnRdRyaTgaIoPXVLS0tsNpdlGaqqolaruSvAAFigC7frle/+IQzD2HQy85WbTqd31OcAFew+qL9CO3r0KGuQy+WY3Wq1WmzSO3/+PFOyJElotVqYnZ0N+cgAWHltda1rDtjR57p3E5FIJKDrOtrtduh80F0Lln2fWNd1JBKJ/ih44+QStE/+m06n04jFYgy0P5H4KvXrZFnumVC67hf72LcHkM/JaEw1kMvlMDs7u6M+vmjkc3J/FPxVTsdPHkM8EYemaT3ewlZwNU1DPBHvS1yC84MtQX8xaJ98NauqipWVFRiGgaGhIRQKha6v6y2Tg3XB4dj1S9nHvj7Er98eQyQSgaqqUBSF/WbQD26321AUBdPT04hEIhjNjPZvkvNvZDAyiLXVNSwtLbEG+Xye3fSRI0dC4Pw6wzB66vzkX2swMghKA8thUPjv1Pu254d4LvIcyten8dt3itA0DZqmQZIkSJIEURSh6zoTTT+DPWzevnvvLg4dOoTChQK0WQ2iKKLT6YQ8g3K5zGIMyWQS+XyeqbdcLrO2wToAGBoaQrvdxovffxHXSlfxv/V1mOY6TMuEaVqw/biEY8OxHRaE32vo8nEKV7Jgz78X/4WBgUP4aP4jZH6RYcvJbDb7SD/gB1YAYOqdKfzwzA+wbq5j3TRhmSZMawPgRwj4PK4Bdw4A29JJpoYRjUYBAIVCocf12U1aWVlhs3U0GvUC8X5o0oHj2WLfXDypiQMAhzqwbXcf7dLliwyQoiihGO9u4KZSKdZ37M0xL8BudyEHQpRskqVP1pYRm9wB0PH8OF24X6PGgzp99Wev+lM9lSSJ1ut1utPUarWoJEmsv6zI1HhQpwv3a/Ti5Yvs/Ncod79kX8/QxfoCNT42qKzI7LwoinRycpJ2Op0twXY6HTo5OUlFUWT9Tp46SZc+NuiisUDH8+NfR7i0Z/U/kR/Hy4oMQRBwrXgN7//l/T1vGRUuTcKyLNy9W8NrP3/t4IdiwLwEdzOCq9SN3/tmIoJ5Ij/uKvlBnb6n/plGo9Edv7FoNErLvy9T40GdLhoL9N0/vNs3tVBKty0Hz31pCvZT9vUMXvnpK2wXQq9UcWPuxrbb9mfls0gmh9le29zcDUwVpvqnlE0U/GUq96EBwuMnjmEifwHf/k40sBsRDDci5Lf6/3iy/Mkn+N3VEuar8/0digGIj4Np2HEE9vTwaZx56QxOfPcEvhGJhGO4nmv12eoq7i3ew+2bt/sO9iur4KdpHwBTSp8lhHzxFMWBjCjy/wEATHqgDqiBjQoAAAAASUVORK5CYII=",
        "cc-by-sa": common + "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",
        "cc-by-nd": common + "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",
        "cc-by-nc": common + "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",
        "cc-by-nc-sa": common + "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",
        "cc-by-nc-nd": common + "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"
      },
      target;
  
      return {
  
        _setup: function( options ) {
  
          var attrib = "",
          license = options.license && licenses[ options.license.toLowerCase() ],
          tar = "target=_blank";
  
          // make a div to put the information into
          options._container = document.createElement( "div" );
          options._container.style.display = "none";
  
          // Cache declared target
          target = document.getElementById( options.target );
  
          if ( options.nameofworkurl ) {
            attrib += "<a href='" + options.nameofworkurl + "' " + tar + ">";
          }
          if ( options.nameofwork ) {
            attrib += options.nameofwork;
          }
          if ( options.nameofworkurl ) {
            attrib += "</a>";
          }
          if ( options.copyrightholderurl ) {
            attrib += "<a href='" + options.copyrightholderurl + "' " + tar + ">";
          }
          if ( options.copyrightholder ) {
            attrib += ", " + options.copyrightholder;
          }
          if ( options.copyrightholderurl ) {
            attrib += "</a>";
          }
  
          //if the user did not specify any parameters just pull the text from the tag
          if ( attrib === "" ) {
            attrib = options.text;
          }
  
          if ( options.license ) {
            if ( license ) {
              if ( options.licenseurl ) {
                attrib = "<a href='" + options.licenseurl + "' " + tar + "><img src='"+ license +"' border='0'/></a> " + attrib;
              } else {
                attrib = "<img src='"+ license +"' />" + attrib;
              }
            } else {
              attrib += ", license: ";
  
              if ( options.licenseurl ) {
                attrib += "<a href='" + options.licenseurl + "' " + tar + ">" + options.license + "</a> ";
              } else {
                attrib += options.license;
              }
            }
          } else if ( options.licenseurl ) {
            attrib += ", <a href='" + options.licenseurl + "' " + tar + ">license</a> ";
          }
  
          options._container.innerHTML  = attrib;
  
          if ( !target && Popcorn.plugin.debug ) {
            throw new Error( "target container doesn't exist" );
          }
          target && target.appendChild( options._container );
        },
        /**
         * @member attribution
         * The start function will be executed when the currentTime
         * of the video  reaches the start time provided by the
         * options variable
         */
        start: function( event, options ) {
          options._container.style.display = "inline";
        },
        /**
         * @member attribution
         * The end function will be executed when the currentTime
         * of the video  reaches the end time provided by the
         * options variable
         */
        end: function( event, options ) {
          options._container.style.display = "none";
        },
        _teardown: function( options ) {
  
          // Cache declared target
          target = document.getElementById( options.target );
  
          target && target.removeChild( options._container );
        }
      };
    })(),
    {
      about:{
        name: "Popcorn Attribution Plugin",
        version: "0.2",
        author: "@rwaldron",
        website: "github.com/rwldrn"
      },
      options:{
        start: {
         elem: "input",
         type: "text",
         label: "In"
        },
        end: {
          elem: "input",
          type: "text",
          label: "Out"
        },
        nameofwork: {
          elem: "input",
          type: "text",
          label: "Name of Work"
        },
        nameofworkurl: {
          elem: "input",
          type: "url",
          label: "Url of Work",
          optional: true
        },
        copyrightholder: {
          elem: "input",
          type: "text",
          label: "Copyright Holder"
        },
        copyrightholderurl: {
          elem: "input",
          type: "url",
          label: "Copyright Holder Url",
          optional: true
        },
        license: {
          elem: "input",
          type: "text",
          label: "License type"
         },
        licenseurl: {
          elem: "input",
          type: "url",
          label: "License URL",
          optional: true
        },
        target: "attribution-container"
      }
    });
  })( Popcorn );
  
  // PLUGIN: WEBPAGE
  
  (function ( Popcorn ) {
  
    /**
     * Webpages popcorn plug-in
     * Creates an iframe showing a website specified by the user
     * Options parameter will need a start, end, id, target and src.
     * Start is the time that you want this plug-in to execute
     * End is the time that you want this plug-in to stop executing
     * Id is the id that you want assigned to the iframe
     * Target is the id of the document element that the iframe needs to be attached to,
     * this target element must exist on the DOM
     * Src is the url of the website that you want the iframe to display
     *
     * @param {Object} options
     *
     * Example:
       var p = Popcorn('#video')
          .webpage({
            id: "webpages-a",
            start: 5, // seconds
            end: 15, // seconds
            src: 'http://www.webmademovies.org',
            target: 'webpagediv'
          } )
     *
     */
    Popcorn.plugin( "webpage" , {
      manifest: {
        about: {
          name: "Popcorn Webpage Plugin",
          version: "0.1",
          author: "@annasob",
          website: "annasob.wordpress.com"
        },
        options: {
          id: {
            elem: "input",
            type: "text",
            label: "Id",
            optional: true
          },
          start: {
            elem: "input",
            type: "text",
            label: "In"
          },
          end: {
            elem: "input",
            type: "text",
            label: "Out"
          },
          src: {
            elem: "input",
            type: "url",
            label: "Src",
            "default": "http://mozillapopcorn.org"
          },
          target: "iframe-container"
        }
      },
      _setup: function( options ) {
  
        var target = document.getElementById( options.target );
  
        // make src an iframe acceptable string
        options.src = options.src.replace( /^(https?:)?(\/\/)?/, "//" );
  
        // make an iframe
        options._iframe = document.createElement( "iframe" );
        options._iframe.setAttribute( "width", "100%" );
        options._iframe.setAttribute( "height", "100%" );
        options._iframe.id = options.id;
        options._iframe.src = options.src;
        options._iframe.style.display = "none";
  
        if ( !target && Popcorn.plugin.debug ) {
          throw new Error( "target container doesn't exist" );
        }
  
        // add the hidden iframe to the DOM
        target && target.appendChild( options._iframe );
  
      },
      /**
       * @member webpage
       * The start function will be executed when the currentTime
       * of the video  reaches the start time provided by the
       * options variable
       */
      start: function( event, options ){
        // make the iframe visible
        options._iframe.src = options.src;
        options._iframe.style.display = "inline";
      },
      /**
       * @member webpage
       * The end function will be executed when the currentTime
       * of the video  reaches the end time provided by the
       * options variable
       */
      end: function( event, options ){
        // make the iframe invisible
        options._iframe.style.display = "none";
      },
      _teardown: function( options ) {
  
        document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._iframe );
      }
    });
  })( Popcorn );
  
  // PLUGIN: Text
  
  (function ( Popcorn ) {
  
    /**
     * Text Popcorn plug-in
     *
     * Places text in an element on the page.  Plugin options include:
     * Options parameter will need a start, end.
     *   Start: Is the time that you want this plug-in to execute
     *   End: Is the time that you want this plug-in to stop executing
     *   Text: Is the text that you want to appear in the target
     *   Escape: {true|false} Whether to escape the text (e.g., html strings)
     *   Multiline: {true|false} Whether newlines should be turned into <br>s
     *   Target: Is the ID of the element where the text should be placed. An empty target
     *           will be placed on top of the media element
     *
     * @param {Object} options
     *
     * Example:
     *  var p = Popcorn('#video')
     *
     *    // Simple text
     *    .text({
     *      start: 5, // seconds
     *      end: 15, // seconds
     *      text: 'This video made exclusively for drumbeat.org',
     *      target: 'textdiv'
     *     })
     *
     *    // HTML text, rendered as HTML
     *    .text({
     *      start: 15, // seconds
     *      end: 20, // seconds
     *      text: '<p>This video made <em>exclusively</em> for drumbeat.org</p>',
     *      target: 'textdiv'
     *    })
     *
     *    // HTML text, escaped and rendered as plain text
     *    .text({
     *      start: 20, // seconds
     *      end: 25, // seconds
     *      text: 'This is an HTML p element: <p>paragraph</p>',
     *      escape: true,
     *      target: 'textdiv'
     *    })
     *
     *    // Multi-Line HTML text, escaped and rendered as plain text
     *    .text({
     *      start: 25, // seconds
     *      end: 30, // seconds
     *      text: 'This is an HTML p element: <p>paragraph</p>\nThis is an HTML b element: <b>bold</b>',
     *      escape: true,
     *      multiline: true,
     *      target: 'textdiv'
     *    });
     *
     *    // Subtitle text
     *    .text({
     *      start: 30, // seconds
     *      end: 40, // seconds
     *      text: 'This will be overlayed on the video',
     *     })
     **/
  
    /**
     * HTML escape code from mustache.js, used under MIT Licence
     * https://github.com/janl/mustache.js/blob/master/mustache.js
     **/
    var escapeMap = {
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      '"': '&quot;',
      "'": '&#39;'
    };
  
    function escapeHTML( string, multiline ) {
      return String( string ).replace( /&(?!\w+;)|[<>"']/g, function ( s ) {
        return escapeMap[ s ] || s;
      });
    }
  
    function newlineToBreak( string ) {
      // Deal with both \r\n and \n
      return string.replace( /\r?\n/gm, "<br>" );
    }
  
    // Subtitle specific functionality
    function createSubtitleContainer( context, id ) {
  
      var ctxContainer = context.container = document.createElement( "div" ),
          style = ctxContainer.style,
          media = context.media;
  
      var updatePosition = function() {
        var position = context.position();
        // the video element must have height and width defined
        style.fontSize = "18px";
        style.width = media.offsetWidth + "px";
        style.top = position.top  + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px";
        style.left = position.left + "px";
  
        setTimeout( updatePosition, 10 );
      };
  
      ctxContainer.id = id || "";
      style.position = "absolute";
      style.color = "white";
      style.textShadow = "black 2px 2px 6px";
      style.fontWeight = "bold";
      style.textAlign = "center";
  
      updatePosition();
  
      context.media.parentNode.appendChild( ctxContainer );
  
      return ctxContainer;
    }
  
    Popcorn.plugin( "text", {
  
      manifest: {
        about: {
          name: "Popcorn Text Plugin",
          version: "0.1",
          author: "@humphd"
        },
        options: {
          start: {
            elem: "input",
            type: "text",
            label: "In"
          },
          end: {
            elem: "input",
            type: "text",
            label: "Out"
          },
          text: {
            elem: "input",
            type: "text",
            label: "Text",
            "default": "Popcorn.js"
          },
          escape: {
            elem: "input",
            type: "checkbox",
            label: "Escape"
          },
          multiline: {
            elem: "input",
            type: "checkbox",
            label: "Multiline"
          }
        }
      },
  
      _setup: function( options ) {
  
        var target,
            text,
            container = options._container = document.createElement( "div" );
  
        container.style.display = "none";
  
        if ( options.target ) {
          // Try to use supplied target
          target = Popcorn.dom.find( options.target );
  
          if ( !target ) {
            target = createSubtitleContainer( this, options.target );
          }
  
        } else if ( !this.container ) {
          // Creates a div for all subtitles to use
          target = createSubtitleContainer( this );
  
        } else {
          // Use subtitle container
          target = this.container;
        }
  
        // cache reference to actual target container
        options._target = target;
  
        // Escape HTML text if requested
        text = !!options.escape ? escapeHTML( options.text ) :
                                      options.text;
  
        // Swap newline for <br> if requested
        text = !!options.multiline ? newlineToBreak ( text ) : text;
        container.innerHTML = text || "";
  
        target.appendChild( container );
      },
  
      /**
       * @member text
       * The start function will be executed when the currentTime
       * of the video  reaches the start time provided by the
       * options variable
       */
      start: function( event, options ) {
        options._container.style.display = "inline";
      },
  
      /**
       * @member text
       * The end function will be executed when the currentTime
       * of the video  reaches the end time provided by the
       * options variable
       */
      end: function( event, options ) {
        options._container.style.display = "none";
      },
  
      _teardown: function( options ) {
        var target = options._target;
        if ( target ) {
          target.removeChild( options._container );
        }
      }
    });
  })( Popcorn );
  
  
  // PLUGIN: Google Maps
  var googleCallback;
  (function ( Popcorn ) {
  
    var i = 1,
      _mapFired = false,
      _mapLoaded = false,
      geocoder, loadMaps;
    //google api callback
    googleCallback = function ( data ) {
      // ensure all of the maps functions needed are loaded
      // before setting _maploaded to true
      if ( typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng ) {
        geocoder = new google.maps.Geocoder();
        Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js", function() {
          _mapLoaded = true;
        });
      } else {
        setTimeout(function () {
          googleCallback( data );
        }, 1);
      }
    };
    // function that loads the google api
    loadMaps = function () {
      // for some reason the Google Map API adds content to the body
      if ( document.body ) {
        _mapFired = true;
        Popcorn.getScript( "//maps.google.com/maps/api/js?sensor=false&callback=googleCallback" );
      } else {
        setTimeout(function () {
          loadMaps();
        }, 1);
      }
    };
  
    function buildMap( options, location, mapDiv ) {
      var type = options.type ? options.type.toUpperCase() : "HYBRID",
        layer;
  
      // See if we need to make a custom Stamen map layer
      if ( type === "STAMEN-WATERCOLOR" ||
           type === "STAMEN-TERRAIN"    ||
           type === "STAMEN-TONER" ) {
        // Stamen types are lowercase
        layer = type.replace("STAMEN-", "").toLowerCase();
      }
  
      var map = new google.maps.Map( mapDiv, {
        // If a custom layer was specified, use that, otherwise use type
        mapTypeId: layer ? layer : google.maps.MapTypeId[ type ],
        // Hide the layer selection UI
        mapTypeControlOptions: { mapTypeIds: [] }
      });
  
      if ( layer ) {
        map.mapTypes.set( layer, new google.maps.StamenMapType( layer ));
      }
      map.getDiv().style.display = "none";
  
      return map;
    }
  
    /**
     * googlemap popcorn plug-in
     * Adds a map to the target div centered on the location specified by the user
     * Options parameter will need a start, end, target, type, zoom, lat and lng, and location
     * -Start is the time that you want this plug-in to execute
     * -End is the time that you want this plug-in to stop executing
     * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM
     * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW, or one of the
     *                          Stamen custom map types (http://http://maps.stamen.com): STAMEN-TONER,
     *                          STAMEN-WATERCOLOR, or STAMEN-TERRAIN.
     * -Zoom [optional] defaults to 0
     * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect)
     * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended)
     * -Lat and Lng: the coordinates of the map must be present if location is not specified.
     * -Height [optional] the height of the map, in "px" or "%". Defaults to "100%".
     * -Width [optional] the width of the map, in "px" or "%". Defaults to "100%".
     * -Location: the adress you want the map to display, must be present if lat and lng are not specified.
     * Note: using location requires extra loading time, also not specifying both lat/lng and location will
     * cause and error.
     *
     * Tweening works using the following specifications:
     * -location is the start point when using an auto generated route
     * -tween when used in this context is a string which specifies the end location for your route
     * Note that both location and tween must be present when using an auto generated route, or the map will not tween
     * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
     * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route
     *
     * -tween is an array of objects, each containing data for one frame of a tween
     * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work
     * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0
     * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds )
     *
     * @param {Object} options
     *
     * Example:
     var p = Popcorn("#video")
     .googlemap({
     start: 5, // seconds
     end: 15, // seconds
     type: "ROADMAP",
     target: "map",
     lat: 43.665429,
     lng: -79.403323
     } )
     *
     */
    Popcorn.plugin( "googlemap", function ( options ) {
      var newdiv, map, location,
          target = document.getElementById( options.target );
  
      options.type = options.type || "ROADMAP";
      options.zoom = options.zoom || 1;
      options.lat = options.lat || 0;
      options.lng = options.lng || 0;
  
      // if this is the firest time running the plugins
      // call the function that gets the sctipt
      if ( !_mapFired ) {
        loadMaps();
      }
  
      // create a new div this way anything in the target div is left intact
      // this is later passed on to the maps api
      newdiv = document.createElement( "div" );
      newdiv.id = "actualmap" + i;
      newdiv.style.width = options.width || "100%";
  
      // height is a little more complicated than width.
      if ( options.height ) {
        newdiv.style.height = options.height;
      } else if ( target && target.clientHeight ) {
        newdiv.style.height = target.clientHeight + "px";
      } else {
        newdiv.style.height = "100%";
      }
  
      i++;
  
      // ensure the target container the user chose exists
      if ( !target && Popcorn.plugin.debug ) {
        throw new Error( "target container doesn't exist" );
      }
      target && target.appendChild( newdiv );
  
      // ensure that google maps and its functions are loaded
      // before setting up the map parameters
      var isMapReady = function () {
        if ( _mapLoaded ) {
          if ( options.location ) {
            // calls an anonymous google function called on separate thread
            geocoder.geocode({
              "address": options.location
            }, function ( results, status ) {
              if ( status === google.maps.GeocoderStatus.OK ) {
                options.lat = results[ 0 ].geometry.location.lat();
                options.lng = results[ 0 ].geometry.location.lng();
                location = new google.maps.LatLng( options.lat, options.lng );
                map = buildMap( options, location, newdiv );
              }
            });
          } else {
            location = new google.maps.LatLng( options.lat, options.lng );
            map = buildMap( options, location, newdiv );
          }
        } else {
            setTimeout(function () {
              isMapReady();
            }, 5);
          }
        };
  
      isMapReady();
  
      return {
        /**
         * @member webpage
         * The start function will be executed when the currentTime
         * of the video reaches the start time provided by the
         * options variable
         */
        start: function( event, options ) {
          var that = this,
              sView;
  
          // ensure the map has been initialized in the setup function above
          var isMapSetup = function() {
            if ( map ) {
              map.getDiv().style.display = "block";
              // reset the location and zoom just in case the user plaid with the map
              google.maps.event.trigger( map, "resize" );
              map.setCenter( location );
  
              // make sure options.zoom is a number
              if ( options.zoom && typeof options.zoom !== "number" ) {
                options.zoom = +options.zoom;
              }
  
              map.setZoom( options.zoom );
  
              //Make sure heading is a number
              if ( options.heading && typeof options.heading !== "number" ) {
                options.heading = +options.heading;
              }
              //Make sure pitch is a number
              if ( options.pitch && typeof options.pitch !== "number" ) {
                options.pitch = +options.pitch;
              }
  
              if ( options.type === "STREETVIEW" ) {
                // Switch this map into streeview mode
                map.setStreetView(
                // Pass a new StreetViewPanorama instance into our map
  
                  sView = new google.maps.StreetViewPanorama( newdiv, {
                    position: location,
                    pov: {
                      heading: options.heading = options.heading || 0,
                      pitch: options.pitch = options.pitch || 0,
                      zoom: options.zoom
                    }
                  })
                );
  
                //  Function to handle tweening using a set timeout
                var tween = function( rM, t ) {
  
                  var computeHeading = google.maps.geometry.spherical.computeHeading;
                  setTimeout(function() {
  
                    var current_time = that.media.currentTime;
  
                    //  Checks whether this is a generated route or not
                    if ( typeof options.tween === "object" ) {
  
                      for ( var i = 0, m = rM.length; i < m; i++ ) {
  
                        var waypoint = rM[ i ];
  
                        //  Checks if this position along the tween should be displayed or not
                        if ( current_time >= ( waypoint.interval * ( i + 1 ) ) / 1000 &&
                           ( current_time <= ( waypoint.interval * ( i + 2 ) ) / 1000 ||
                             current_time >= waypoint.interval * ( m ) / 1000 ) ) {
  
                          sView3.setPosition( new google.maps.LatLng( waypoint.position.lat, waypoint.position.lng ) );
  
                          sView3.setPov({
                            heading: waypoint.pov.heading || computeHeading( waypoint, rM[ i + 1 ] ) || 0,
                            zoom: waypoint.pov.zoom || 0,
                            pitch: waypoint.pov.pitch || 0
                          });
                        }
                      }
  
                      //  Calls the tween function again at the interval set by the user
                      tween( rM, rM[ 0 ].interval );
                    } else {
  
                      for ( var k = 0, l = rM.length; k < l; k++ ) {
  
                        var interval = options.interval;
  
                        if( current_time >= (interval * ( k + 1 ) ) / 1000 &&
                          ( current_time <= (interval * ( k + 2 ) ) / 1000 ||
                            current_time >= interval * ( l ) / 1000 ) ) {
  
                          sView2.setPov({
                            heading: computeHeading( rM[ k ], rM[ k + 1 ] ) || 0,
                            zoom: options.zoom,
                            pitch: options.pitch || 0
                          });
                          sView2.setPosition( checkpoints[ k ] );
                        }
                      }
  
                      tween( checkpoints, options.interval );
                    }
                  }, t );
                };
  
                //  Determines if we should use hardcoded values ( using options.tween ),
                //  or if we should use a start and end location and let google generate
                //  the route for us
                if ( options.location && typeof options.tween === "string" ) {
  
                //  Creating another variable to hold the streetview map for tweening,
                //  Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps
                var sView2 = sView;
  
                  //  Create an array to store all the lat/lang values along our route
                  var checkpoints = [];
  
                  //  Creates a new direction service, later used to create a route
                  var directionsService = new google.maps.DirectionsService();
  
                  //  Creates a new direction renderer using the current map
                  //  This enables us to access all of the route data that is returned to us
                  var directionsDisplay = new google.maps.DirectionsRenderer( sView2 );
  
                  var request = {
                    origin: options.location,
                    destination: options.tween,
                    travelMode: google.maps.TravelMode.DRIVING
                  };
  
                  //  Create the route using the direction service and renderer
                  directionsService.route( request, function( response, status ) {
  
                    if ( status == google.maps.DirectionsStatus.OK ) {
                      directionsDisplay.setDirections( response );
                      showSteps( response, that );
                    }
  
                  });
  
                  var showSteps = function ( directionResult, that ) {
  
                    //  Push new google map lat and lng values into an array from our list of lat and lng values
                    var routes = directionResult.routes[ 0 ].overview_path;
                    for ( var j = 0, k = routes.length; j < k; j++ ) {
                      checkpoints.push( new google.maps.LatLng( routes[ j ].lat(), routes[ j ].lng() ) );
                    }
  
                    //  Check to make sure the interval exists, if not, set to a default of 1000
                    options.interval = options.interval || 1000;
                    tween( checkpoints, 10 );
  
                  };
                } else if ( typeof options.tween === "object" ) {
  
                  //  Same as the above to stop streetview maps from overflowing into one another
                  var sView3 = sView;
  
                  for ( var i = 0, l = options.tween.length; i < l; i++ ) {
  
                    //  Make sure interval exists, if not, set to 1000
                    options.tween[ i ].interval = options.tween[ i ].interval || 1000;
                    tween( options.tween, 10 );
                  }
                }
              }
            } else {
              setTimeout(function () {
                isMapSetup();
              }, 13);
            }
          };
          isMapSetup();
        },
        /**
         * @member webpage
         * The end function will be executed when the currentTime
         * of the video reaches the end time provided by the
         * options variable
         */
        end: function ( event, options ) {
          // if the map exists hide it do not delete the map just in
          // case the user seeks back to time b/w start and end
          if ( map ) {
            map.getDiv().style.display = "none";
          }
        },
        _teardown: function ( options ) {
  
          var target = document.getElementById( options.target );
  
          // the map must be manually removed
          target && target.removeChild( newdiv );
          newdiv = map = location = null;
        }
      };
    }, {
      about: {
        name: "Popcorn Google Map Plugin",
        version: "0.1",
        author: "@annasob",
        website: "annasob.wordpress.com"
      },
      options: {
        start: {
          elem: "input",
          type: "text",
          label: "In"
        },
        end: {
          elem: "input",
          type: "text",
          label: "Out"
        },
        target: "map-container",
        type: {
          elem: "select",
          options: [ "ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN", "STAMEN-WATERCOLOR", "STAMEN-TERRAIN", "STAMEN-TONER" ],
          label: "Type",
          optional: true
        },
        zoom: {
          elem: "input",
          type: "text",
          label: "Zoom",
          "default": 0,
          optional: true
        },
        lat: {
          elem: "input",
          type: "text",
          label: "Lat",
          optional: true
        },
        lng: {
          elem: "input",
          type: "text",
          label: "Lng",
          optional: true
        },
        location: {
          elem: "input",
          type: "text",
          label: "Location",
          "default": "Toronto, Ontario, Canada"
        },
        heading: {
          elem: "input",
          type: "text",
          label: "Heading",
          "default": 0,
          optional: true
        },
        pitch: {
          elem: "input",
          type: "text",
          label: "Pitch",
          "default": 1,
          optional: true
        }
      }
    });
  })( Popcorn );
  
  
  // PLUGIN: IMAGE
  
  (function ( Popcorn ) {
  
  /**
   * Images popcorn plug-in
   * Shows an image element
   * Options parameter will need a start, end, href, target and src.
   * Start is the time that you want this plug-in to execute
   * End is the time that you want this plug-in to stop executing
   * href is the url of the destination of a anchor - optional
   * Target is the id of the document element that the iframe needs to be attached to,
   * this target element must exist on the DOM
   * Src is the url of the image that you want to display
   * text is the overlayed text on the image - optional
   *
   * @param {Object} options
   *
   * Example:
     var p = Popcorn('#video')
        .image({
          start: 5, // seconds
          end: 15, // seconds
          href: 'http://www.drumbeat.org/',
          src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png',
          text: 'DRUMBEAT',
          target: 'imagediv'
        } )
   *
   */
    Popcorn.plugin( "image", {
        manifest: {
          about: {
            name: "Popcorn image Plugin",
            version: "0.1",
            author: "Scott Downe",
            website: "http://scottdowne.wordpress.com/"
          },
          options: {
            start: {
              elem: "input",
              type: "number",
              label: "In"
            },
            end: {
              elem: "input",
              type: "number",
              label: "Out"
            },
            href: {
              elem: "input",
              type: "url",
              label: "anchor URL",
              "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png",
              optional: true
            },
            target: "image-container",
            src: {
              elem: "input",
              type: "url",
              label: "Source URL",
              "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png"
            },
            text: {
              elem: "input",
              type: "text",
              label: "Text",
              optional: true
            }
          }
        },
        _setup: function( options ) {
          var img = document.createElement( "img" ),
              target = document.getElementById( options.target );
  
          options.anchor = document.createElement( "a" );
          options.anchor.style.position = "relative";
          options.anchor.style.textDecoration = "none";
          options.anchor.style.display = "none";
  
  
          if ( !target && Popcorn.plugin.debug ) {
            throw new Error( "target container doesn't exist" );
          }
          // add the widget's div to the target div
          target && target.appendChild( options.anchor );
  
          img.addEventListener( "load", function() {
  
            // borders look really bad, if someone wants it they can put it on their div target
            img.style.borderStyle = "none";
  
            options.anchor.href = options.href || options.src || "#";
            options.anchor.target = "_blank";
  
            var fontHeight, divText;
  
            img.style.height = target.style.height;
            img.style.width = target.style.width;
  
            options.anchor.appendChild( img );
  
            // If display text was provided, display it:
            if ( options.text ) {
              fontHeight = ( img.height / 12 ) + "px";
              divText = document.createElement( "div" );
  
              Popcorn.extend( divText.style, {
                color: "black",
                fontSize: fontHeight,
                fontWeight: "bold",
                position: "relative",
                textAlign: "center",
                width: img.style.width || img.width + "px",
                zIndex: "10"
              });
  
              divText.innerHTML = options.text || "";
  
              divText.style.top = ( ( img.style.height.replace( "px", "" ) || img.height ) / 2 ) - ( divText.offsetHeight / 2 ) + "px";
              options.anchor.insertBefore( divText, img );
            }
          }, false );
  
          img.src = options.src;
        },
  
        /**
         * @member image
         * The start function will be executed when the currentTime
         * of the video  reaches the start time provided by the
         * options variable
         */
        start: function( event, options ) {
          options.anchor.style.display = "inline";
        },
        /**
         * @member image
         * The end function will be executed when the currentTime
         * of the video  reaches the end time provided by the
         * options variable
         */
        end: function( event, options ) {
          options.anchor.style.display = "none";
        },
        _teardown: function( options ) {
          document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.anchor );
        }
    });
  })( Popcorn );
  
  // PLUGIN: TWITTER
  
  (function ( Popcorn ) {
    var scriptLoading = false;
  
    /**
     * Twitter popcorn plug-in
     * Appends a Twitter widget to an element on the page.
     * Options parameter will need a start, end, target and source.
     * Optional parameters are height and width.
     * Start is the time that you want this plug-in to execute
     * End is the time that you want this plug-in to stop executing
     * Src is the hash tag or twitter user to get tweets from
     * Target is the id of the document element that the images are
     *  appended to, this target element must exist on the DOM
     * Height is the height of the widget, defaults to 200
     * Width is the width of the widget, defaults to 250
     *
     * @param {Object} options
     *
     * Example:
       var p = Popcorn('#video')
          .twitter({
            start:          5,                // seconds, mandatory
            end:            15,               // seconds, mandatory
            src:            '@stevesong',     // mandatory, also accepts hash tags
            height:         200,              // optional
            width:          250,              // optional
            target:         'twitterdiv'      // mandatory
          } )
     *
     */
  
    Popcorn.plugin( "twitter" , {
  
        manifest: {
          about: {
            name: "Popcorn Twitter Plugin",
            version: "0.1",
            author: "Scott Downe",
            website: "http://scottdowne.wordpress.com/"
          },
          options:{
            start: {
              elem: "input",
              type: "text",
              label: "In"
            },
            end: {
              elem: "input",
              type: "text",
              label: "Out"
            },
            src: {
              elem: "input",
              type: "text",
              label: "Source",
              "default": "@popcornjs"
            },
            target: "twitter-container",
            height: {
              elem: "input",
              type: "number",
              label: "Height",
              "default": "200",
              optional: true
            },
            width: {
              elem: "input",
              type: "number",
              label: "Width",
              "default": "250",
              optional: true
            }
          }
        },
  
        _setup: function( options ) {
  
          if ( !window.TWTR && !scriptLoading ) {
            scriptLoading = true;
            Popcorn.getScript( "//widgets.twimg.com/j/2/widget.js" );
          }
  
          var target = document.getElementById( options.target );
          // create the div to store the widget
          // setup widget div that is unique per track
          options.container = document.createElement( "div" );
          // use this id to connect it to the widget
          options.container.setAttribute( "id", Popcorn.guid() );
          // display none by default
          options.container.style.display = "none";
  
          if ( !target && Popcorn.plugin.debug ) {
            throw new Error( "target container doesn't exist" );
          }
           // add the widget's div to the target div
          target && target.appendChild( options.container );
  
          // setup info for the widget
          var src = options.src || "",
              width = options.width || 250,
              height = options.height || 200,
              profile = /^@/.test( src ),
              widgetOptions = {
                version: 2,
                // use this id to connect it to the div
                id: options.container.getAttribute( "id" ),
                rpp: 30,
                width: width,
                height: height,
                interval: 6000,
                theme: {
                  shell: {
                    background: "#ffffff",
                    color: "#000000"
                  },
                  tweets: {
                    background: "#ffffff",
                    color: "#444444",
                    links: "#1985b5"
                  }
                },
                features: {
                  loop: true,
                  timestamp: true,
                  avatars: true,
                  hashtags: true,
                  toptweets: true,
                  live: true,
                  scrollbar: false,
                  behavior: 'default'
                }
              };
  
          // create widget
          var isReady = function( that ) {
            if ( window.TWTR ) {
              if ( profile ) {
  
                widgetOptions.type = "profile";
  
                new TWTR.Widget( widgetOptions ).render().setUser( src ).start();
  
              } else {
  
                widgetOptions.type = "search";
                widgetOptions.search = src;
                widgetOptions.subject = src;
  
                new TWTR.Widget( widgetOptions ).render().start();
  
              }
            } else {
              setTimeout( function() {
                isReady( that );
              }, 1);
            }
          };
  
          isReady( this );
        },
  
        /**
         * @member Twitter
         * The start function will be executed when the currentTime
         * of the video  reaches the start time provided by the
         * options variable
         */
        start: function( event, options ) {
          options.container.style.display = "inline";
        },
  
        /**
         * @member Twitter
         * The end function will be executed when the currentTime
         * of the video  reaches the end time provided by the
         * options variable
         */
        end: function( event, options ) {
          options.container.style.display = "none";
        },
        _teardown: function( options ) {
  
          document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options.container );
        }
      });
  
  })( Popcorn );
  
  // PLUGIN: WIKIPEDIA
  
  
  var wikiCallback;
  
  (function ( Popcorn ) {
  
    /**
     * Wikipedia popcorn plug-in
     * Displays a wikipedia aricle in the target specified by the user by using
     * new DOM element instead overwriting them
     * Options parameter will need a start, end, target, lang, src, title and numberofwords.
     * -Start is the time that you want this plug-in to execute
     * -End is the time that you want this plug-in to stop executing
     * -Target is the id of the document element that the text from the article needs to be
     * attached to, this target element must exist on the DOM
     * -Lang (optional, defaults to english) is the language in which the article is in.
     * -Src is the url of the article
     * -Title (optional) is the title of the article
     * -numberofwords (optional, defaults to 200) is  the number of words you want displaid.
     *
     * @param {Object} options
     *
     * Example:
       var p = Popcorn("#video")
          .wikipedia({
            start: 5, // seconds
            end: 15, // seconds
            src: "http://en.wikipedia.org/wiki/Cape_Town",
            target: "wikidiv"
          } )
     *
     */
    Popcorn.plugin( "wikipedia" , {
  
      manifest: {
        about:{
          name: "Popcorn Wikipedia Plugin",
          version: "0.1",
          author: "@annasob",
          website: "annasob.wordpress.com"
        },
        options:{
          start: {
            elem: "input",
            type: "text",
            label: "In"
          },
          end: {
            elem: "input",
            type: "text",
            label: "Out"
          },
          lang: {
            elem: "input",
            type: "text",
            label: "Language",
            "default": "english",
            optional: true
          },
          src: {
            elem: "input", 
            type: "url", 
            label: "Src",
            "default": "http://en.wikipedia.org/wiki/Cat"
          },
          title: {
            elem: "input",
            type: "text",
            label: "Title",
            "default": "Cats",
            optional: true
          },
          numberofwords: {
            elem: "input",
            type: "text",
            label: "Num Of Words",
            "default": "200",
            optional: true
          },
          target: "wikipedia-container"
        }
      },
      /**
       * @member wikipedia
       * The setup function will get all of the needed
       * items in place before the start function is called.
       * This includes getting data from wikipedia, if the data
       * is not received and processed before start is called start
       * will not do anything
       */
      _setup : function( options ) {
        // declare needed variables
        // get a guid to use for the global wikicallback function
        var  _text, _guid = Popcorn.guid();
  
        // if the user didn't specify a language default to english
        if ( !options.lang ) {
          options.lang = "en";
        }
  
        // if the user didn't specify number of words to use default to 200
        options.numberofwords  = options.numberofwords || 200;
  
        // wiki global callback function with a unique id
        // function gets the needed information from wikipedia
        // and stores it by appending values to the options object
        window[ "wikiCallback" + _guid ]  = function ( data ) {
  
          options._link = document.createElement( "a" );
          options._link.setAttribute( "href", options.src );
          options._link.setAttribute( "target", "_blank" );
  
          // add the title of the article to the link
          options._link.innerHTML = options.title || data.parse.displaytitle;
  
          // get the content of the wiki article
          options._desc = document.createElement( "p" );
  
          // get the article text and remove any special characters
          _text = data.parse.text[ "*" ].substr( data.parse.text[ "*" ].indexOf( "<p>" ) );
          _text = _text.replace( /((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g, "" );
  
          _text = _text.split( " " );
          options._desc.innerHTML = ( _text.slice( 0, ( _text.length >= options.numberofwords ? options.numberofwords : _text.length ) ).join (" ") + " ..." ) ;
  
          options._fired = true;
        };
  
        if ( options.src ) {
          Popcorn.getScript( "//" + options.lang + ".wikipedia.org/w/api.php?action=parse&props=text&redirects&page=" +
            options.src.slice( options.src.lastIndexOf( "/" ) + 1 )  + "&format=json&callback=wikiCallback" + _guid );
        } else if ( Popcorn.plugin.debug ) {
          throw new Error( "Wikipedia plugin needs a 'src'" );
        }
  
      },
      /**
       * @member wikipedia
       * The start function will be executed when the currentTime
       * of the video  reaches the start time provided by the
       * options variable
       */
      start: function( event, options ){
        // dont do anything if the information didn't come back from wiki
        var isReady = function () {
  
          if ( !options._fired ) {
            setTimeout( function () {
              isReady();
            }, 13);
          } else {
  
            if ( options._link && options._desc ) {
              if ( document.getElementById( options.target ) ) {
                document.getElementById( options.target ).appendChild( options._link );
                document.getElementById( options.target ).appendChild( options._desc );
                options._added = true;
              }
            }
          }
        };
  
        isReady();
      },
      /**
       * @member wikipedia
       * The end function will be executed when the currentTime
       * of the video  reaches the end time provided by the
       * options variable
       */
      end: function( event, options ){
        // ensure that the data was actually added to the
        // DOM before removal
        if ( options._added ) {
          document.getElementById( options.target ).removeChild( options._link );
          document.getElementById( options.target ).removeChild( options._desc );
        }
      },
  
      _teardown: function( options ){
  
        if ( options._added ) {
          options._link.parentNode && document.getElementById( options.target ).removeChild( options._link );
          options._desc.parentNode && document.getElementById( options.target ).removeChild( options._desc );
          delete options.target;
        }
      }
    });
  
  })( Popcorn );
  
  (function( Popcorn ) {
  
    // combines calls of two function calls into one
    var combineFn = function( first, second ) {
  
      first = first || Popcorn.nop;
      second = second || Popcorn.nop;
  
      return function() {
  
        first.apply( this, arguments );
        second.apply( this, arguments );
      };
    };
  
    //  ID string matching
    var rIdExp  = /^(#([\w\-\_\.]+))$/;
  
    var audioExtensions = "ogg|oga|aac|mp3|wav",
        videoExtensions = "ogg|ogv|mp4|webm",
        mediaExtensions = audioExtensions + "|" + videoExtensions;
  
    var audioExtensionRegexp = new RegExp( "^.*\\.(" + audioExtensions + ")$" ),
        mediaExtensionRegexp = new RegExp( "^.*\\.(" + mediaExtensions + ")$" );
  
    Popcorn.player = function( name, player ) {
  
      // return early if a player already exists under this name
      if ( Popcorn[ name ] ) {
  
        return;
      }
  
      player = player || {};
  
      var playerFn = function( target, src, options ) {
  
        options = options || {};
  
        // List of events
        var date = new Date() / 1000,
            baselineTime = date,
            currentTime = 0,
            readyState = 0,
            volume = 1,
            muted = false,
            events = {},
  
            // The container div of the resource
            container = typeof target === "string" ? Popcorn.dom.find( target ) : target,
            basePlayer = {},
            timeout,
            popcorn;
  
        if ( !Object.prototype.__defineGetter__ ) {
  
          basePlayer = container || document.createElement( "div" );
        }
  
        // copies a div into the media object
        for( var val in container ) {
  
          // don't copy properties if using container as baseplayer
          if ( val in basePlayer ) {
  
            continue;
          }
  
          if ( typeof container[ val ] === "object" ) {
  
            basePlayer[ val ] = container[ val ];
          } else if ( typeof container[ val ] === "function" ) {
  
            basePlayer[ val ] = (function( value ) {
  
              // this is a stupid ugly kludgy hack in honour of Safari
              // in Safari a NodeList is a function, not an object
              if ( "length" in container[ value ] && !container[ value ].call ) {
  
                return container[ value ];
              } else {
  
                return function() {
  
                  return container[ value ].apply( container, arguments );
                };
              }
            }( val ));
          } else {
  
            Popcorn.player.defineProperty( basePlayer, val, {
              get: (function( value ) {
  
                return function() {
  
                  return container[ value ];
                };
              }( val )),
              set: Popcorn.nop,
              configurable: true
            });
          }
        }
  
        var timeupdate = function() {
  
          date = new Date() / 1000;
  
          if ( !basePlayer.paused ) {
  
            basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime );
            basePlayer.dispatchEvent( "timeupdate" );
            timeout = setTimeout( timeupdate, 10 );
          }
  
          baselineTime = date;
        };
  
        basePlayer.play = function() {
  
          this.paused = false;
  
          if ( basePlayer.readyState >= 4 ) {
  
            baselineTime = new Date() / 1000;
            basePlayer.dispatchEvent( "play" );
            timeupdate();
          }
        };
  
        basePlayer.pause = function() {
  
          this.paused = true;
          basePlayer.dispatchEvent( "pause" );
        };
  
        Popcorn.player.defineProperty( basePlayer, "currentTime", {
          get: function() {
  
            return currentTime;
          },
          set: function( val ) {
  
            // make sure val is a number
            currentTime = +val;
            basePlayer.dispatchEvent( "timeupdate" );
  
            return currentTime;
          },
          configurable: true
        });
  
        Popcorn.player.defineProperty( basePlayer, "volume", {
          get: function() {
  
            return volume;
          },
          set: function( val ) {
  
            // make sure val is a number
            volume = +val;
            basePlayer.dispatchEvent( "volumechange" );
            return volume;
          },
          configurable: true
        });
  
        Popcorn.player.defineProperty( basePlayer, "muted", {
          get: function() {
  
            return muted;
          },
          set: function( val ) {
  
            // make sure val is a number
            muted = +val;
            basePlayer.dispatchEvent( "volumechange" );
            return muted;
          },
          configurable: true
        });
  
        Popcorn.player.defineProperty( basePlayer, "readyState", {
          get: function() {
  
            return readyState;
          },
          set: function( val ) {
  
            readyState = val;
            return readyState;
          },
          configurable: true
        });
  
        // Adds an event listener to the object
        basePlayer.addEventListener = function( evtName, fn ) {
  
          if ( !events[ evtName ] ) {
  
            events[ evtName ] = [];
          }
  
          events[ evtName ].push( fn );
          return fn;
        };
  
        // Removes an event listener from the object
        basePlayer.removeEventListener = function( evtName, fn ) {
  
          var i,
              listeners = events[ evtName ];
  
          if ( !listeners ){
  
            return;
          }
  
          // walk backwards so we can safely splice
          for ( i = events[ evtName ].length - 1; i >= 0; i-- ) {
  
            if( fn === listeners[ i ] ) {
  
              listeners.splice(i, 1);
            }
          }
  
          return fn;
        };
  
        // Can take event object or simple string
        basePlayer.dispatchEvent = function( oEvent ) {
  
          var evt,
              self = this,
              eventInterface,
              eventName = oEvent.type;
  
          // A string was passed, create event object
          if ( !eventName ) {
  
            eventName = oEvent;
            eventInterface  = Popcorn.events.getInterface( eventName );
  
            if ( eventInterface ) {
  
              evt = document.createEvent( eventInterface );
              evt.initEvent( eventName, true, true, window, 1 );
            }
          }
  
          if ( events[ eventName ] ) {
  
            for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) {
  
              events[ eventName ][ i ].call( self, evt, self );
            }
          }
        };
  
        // Attempt to get src from playerFn parameter
        basePlayer.src = src || "";
        basePlayer.duration = 0;
        basePlayer.paused = true;
        basePlayer.ended = 0;
  
        options && options.events && Popcorn.forEach( options.events, function( val, key ) {
  
          basePlayer.addEventListener( key, val, false );
        });
  
        // true and undefined returns on canPlayType means we should attempt to use it,
        // false means we cannot play this type
        if ( player._canPlayType( container.nodeName, src ) !== false ) {
  
          if ( player._setup ) {
  
            player._setup.call( basePlayer, options );
          } else {
  
            // there is no setup, which means there is nothing to load
            basePlayer.readyState = 4;
            basePlayer.dispatchEvent( "loadedmetadata" );
            basePlayer.dispatchEvent( "loadeddata" );
            basePlayer.dispatchEvent( "canplaythrough" );
          }
        } else {
  
          basePlayer.dispatchEvent( "error" );
        }
  
        popcorn = new Popcorn.p.init( basePlayer, options );
  
        if ( player._teardown ) {
  
          popcorn.destroy = combineFn( popcorn.destroy, function() {
  
            player._teardown.call( basePlayer, options );
          });
        }
  
        return popcorn;
      };
  
      playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop;
  
      Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn;
    };
  
    Popcorn.player.registry = {};
  
    Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
  
      object.__defineGetter__( description, options.get || Popcorn.nop );
      object.__defineSetter__( description, options.set || Popcorn.nop );
    };
  
    // player queue is to help players queue things like play and pause
    // HTML5 video's play and pause are asynch, but do fire in sequence
    // play() should really mean "requestPlay()" or "queuePlay()" and
    // stash a callback that will play the media resource when it's ready to be played
    Popcorn.player.playerQueue = function() {
  
      var _queue = [],
          _running = false;
  
      return {
        next: function() {
  
          _running = false;
          _queue.shift();
          _queue[ 0 ] && _queue[ 0 ]();
        },
        add: function( callback ) {
  
          _queue.push(function() {
  
            _running = true;
            callback && callback();
          });
  
          // if there is only one item on the queue, start it
          !_running && _queue[ 0 ]();
        }
      };
    };
  
    // smart will attempt to find you a match, if it does not find a match,
    // it will attempt to create a video element with the source,
    // if that failed, it will throw.
    Popcorn.smart = function( target, src, options ) {
      var playerType,
          elementTypes = [ "AUDIO", "VIDEO" ],
          sourceNode,
          firstSrc,
          node = Popcorn.dom.find( target ),
          i, srcResult,
          canPlayTypeTester = document.createElement( "video" ),
          canPlayTypes = {
            "ogg": "video/ogg",
            "ogv": "video/ogg",
            "oga": "audio/ogg",
            "webm": "video/webm",
            "mp4": "video/mp4",
            "mp3": "audio/mp3"
          };
  
      var canPlayType = function( type ) {
  
        return canPlayTypeTester.canPlayType( canPlayTypes[ type ] );
      };
  
      var canPlaySrc = function( src ) {
  
        srcResult = mediaExtensionRegexp.exec( src );
  
        if ( !srcResult || !srcResult[ 1 ] ) {
          return false;
        }
  
        return canPlayType( srcResult[ 1 ] );
      };
  
      if ( !node ) {
  
        Popcorn.error( "Specified target " + target + " was not found." );
        return;
      }
  
      // For when no src is defined.
      // Usually this is a video element with a src already on it.
      if ( elementTypes.indexOf( node.nodeName ) > -1 && !src ) {
  
        if ( typeof src === "object" ) {
  
          options = src;
          src = undefined;
        }
  
        return Popcorn( node, options );
      }
  
      // if our src is not an array, create an array of one.	
      if ( typeof( src ) === "string" ) {
  
        src = [ src ];
      }
  
      // go through each src, and find the first playable.
      // this only covers player sources popcorn knows of,
      // and not things like a youtube src that is private.
      // it will still consider a private youtube video to be playable.
      for ( i = 0, srcLength = src.length; i < srcLength; i++ ) {
  
        // src is a playable HTML5 video, we don't need to check custom players.
        if ( canPlaySrc( src[ i ] ) ) {
  
          src = src[ i ];
          break;
        }
  
        // for now we loop through and use the first valid player we find.
        for ( var key in Popcorn.player.registry ) {
  
          if ( Popcorn.player.registry.hasOwnProperty( key ) ) {
  
            if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, src[ i ] ) ) {
  
              // Popcorn.smart( player, src, /* options */ )
              return Popcorn[ key ]( node, src[ i ], options );
            }
          }
        }
      }
  
      // Popcorn.smart( div, src, /* options */ )
      // attempting to create a video in a container
      if ( elementTypes.indexOf( node.nodeName ) === -1 ) {
  
        firstSrc = typeof( src ) === "string" ? src : src.length ? src[ 0 ] : src;
  
        target = document.createElement( !!audioExtensionRegexp.exec( firstSrc ) ? elementTypes[ 0 ] : elementTypes[ 1 ] );
  
        node.appendChild( target );
        node = target;
      }
  
      options && options.events && options.events.error && node.addEventListener( "error", options.events.error, false );
      node.src = src;
  
      return Popcorn( node, options );
  
    };
  })( Popcorn );
  
  // A global callback for youtube... that makes me angry
  var onYouTubePlayerAPIReady = function() {
  
    onYouTubePlayerAPIReady.ready = true;
    for ( var i = 0; i < onYouTubePlayerAPIReady.waiting.length; i++ ) {
      onYouTubePlayerAPIReady.waiting[ i ]();
    }
  };
  
  onYouTubePlayerAPIReady.waiting = [];
  
  Popcorn.getScript( "http://www.youtube.com/player_api" );
  
  Popcorn.player( "youtube", {
    _canPlayType: function( nodeName, url ) {
  
      return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu)/).test( url ) && nodeName.toLowerCase() !== "video";
    },
    _setup: function( options ) {
  
      var media = this,
          autoPlay = false,
          container = document.createElement( "div" ),
          currentTime = 0,
          paused = true,
          seekTime = 0,
          firstGo = true,
          seeking = false,
          fragmentStart = 0,
  
          // state code for volume changed polling
          lastMuted = false,
          lastVolume = 100,
          playerQueue = Popcorn.player.playerQueue();
  
      var createProperties = function() {
  
        Popcorn.player.defineProperty( media, "currentTime", {
          set: function( val ) {
  
            if ( options.destroyed ) {
              return;
            }
  
            seeking = true;
            // make sure val is a number
            currentTime = Math.round( +val * 100 ) / 100;
          },
          get: function() {
  
            return currentTime;
          }
        });
  
        Popcorn.player.defineProperty( media, "paused", {
          get: function() {
  
            return paused;
          }
        });
  
        Popcorn.player.defineProperty( media, "muted", {
          set: function( val ) {
  
            if ( options.destroyed ) {
  
              return val;
            }
  
            if ( options.youtubeObject.isMuted() !== val ) {
  
              if ( val ) {
  
                options.youtubeObject.mute();
              } else {
  
                options.youtubeObject.unMute();
              }
  
              lastMuted = options.youtubeObject.isMuted();
              media.dispatchEvent( "volumechange" );
            }
  
            return options.youtubeObject.isMuted();
          },
          get: function() {
  
            if ( options.destroyed ) {
  
              return 0;
            }
  
            return options.youtubeObject.isMuted();
          }
        });
  
        Popcorn.player.defineProperty( media, "volume", {
          set: function( val ) {
  
            if ( options.destroyed ) {
  
              return val;
            }
  
            if ( options.youtubeObject.getVolume() / 100 !== val ) {
  
              options.youtubeObject.setVolume( val * 100 );
              lastVolume = options.youtubeObject.getVolume();
              media.dispatchEvent( "volumechange" );
            }
  
            return options.youtubeObject.getVolume() / 100;
          },
          get: function() {
  
            if ( options.destroyed ) {
  
              return 0;
            }
  
            return options.youtubeObject.getVolume() / 100;
          }
        });
  
        media.play = function() {
  
          if ( options.destroyed ) {
  
            return;
          }
  
          paused = false;
          playerQueue.add(function() {
  
            if ( options.youtubeObject.getPlayerState() !== 1 ) {
  
              seeking = false;
              options.youtubeObject.playVideo();
            } else {
              playerQueue.next();
            }
          });
        };
  
        media.pause = function() {
  
          if ( options.destroyed ) {
  
            return;
          }
  
          paused = true;
          playerQueue.add(function() {
  
            if ( options.youtubeObject.getPlayerState() !== 2 ) {
  
              options.youtubeObject.pauseVideo();
            } else {
              playerQueue.next();
            }
          });
        };
      };
  
      container.id = media.id + Popcorn.guid();
      options._container = container;
      media.appendChild( container );
  
      var youtubeInit = function() {
  
        var src, width, height, originalStyle, query;
  
        var timeUpdate = function() {
  
          if ( options.destroyed ) {
            return;
          }
  
          if ( !seeking ) {
            currentTime = options.youtubeObject.getCurrentTime();
            media.dispatchEvent( "timeupdate" );
          } else if ( currentTime === options.youtubeObject.getCurrentTime() ) {
  
            seeking = false;
            media.dispatchEvent( "seeked" );
            media.dispatchEvent( "timeupdate" );
          } else {
  
            // keep trying the seek until it is right.
            options.youtubeObject.seekTo( currentTime );
          }
          setTimeout( timeUpdate, 250 );
        };
  
        options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1;
        options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1;
  
        src = /^.*(?:\/|v=)(.{11})/.exec( media.src )[ 1 ];
  
        query = ( media.src.split( "?" )[ 1 ] || "" )
                           .replace( /v=.{11}/, "" );
        query = query.replace( /&t=(?:(\d+)m)?(?:(\d+)s)?/, function( all, minutes, seconds ) {
  
          // Make sure we have real zeros
          minutes = minutes | 0; // bit-wise OR
          seconds = seconds | 0; // bit-wise OR
  
          fragmentStart = ( +seconds + ( minutes * 60 ) );
          return "";
        });
        query = query.replace( /&start=(\d+)?/, function( all, seconds ) {
  
          // Make sure we have real zeros
          seconds = seconds | 0; // bit-wise OR
  
          fragmentStart = seconds;
          return "";
        });
  
        autoPlay = ( /autoplay=1/.test( query ) );
  
        // cache original display property so it can be reapplied
        originalStyle = media.style.display;
        media.style.display = "inline";
  
        // setting youtube player's height and width, min 640 x 390,
        // anything smaller, and the player reports incorrect states.
        height = media.clientHeight >= 390 ? "" + media.clientHeight : "390";
        width = media.clientWidth >= 640 ? "" + media.clientWidth : "640";
        
        media.style.display = originalStyle;
  
        options.youtubeObject = new YT.Player( container.id, {
          height: height,
          width: width,
          videoId: src,
          events: {
            "onReady": function(){
  
              // pulling initial volume states form baseplayer
              lastVolume = media.volume;
              lastMuted = media.muted;
  
              media.duration = options.youtubeObject.getDuration();
  
              media.dispatchEvent( "durationchange" );
              volumeupdate();
  
              // pulling initial paused state from autoplay or the baseplayer
              // also need to explicitly set to paused otherwise.
              if ( autoPlay || !media.paused ) {
                paused = false;
              }
  
              createProperties();
              options.youtubeObject.playVideo();
  
              if ( paused ) {
                options.youtubeObject.pauseVideo();
              }
  
              media.currentTime = fragmentStart;
  
              media.dispatchEvent( "loadedmetadata" );
              media.dispatchEvent( "loadeddata" );
              media.readyState = 4;
  
              timeUpdate();
              media.dispatchEvent( "canplaythrough" );
            },
            "onStateChange": function( state ){
  
              if ( options.destroyed || state.data === -1 ) {
                return;
              }
  
              // state.data === 2 is for pause events
              // state.data === 1 is for play events
              if ( state.data === 2 ) {
  
                paused = true;
                media.dispatchEvent( "pause" );
                playerQueue.next();
              } else if ( state.data === 1 ) {
                paused = false;
                media.dispatchEvent( "play" );
                media.dispatchEvent( "playing" );
                playerQueue.next();
              } else if ( state.data === 0 ) {
                media.dispatchEvent( "ended" );
              }
            },
            "onError": function( error ) {
  
              if ( [ 2, 100, 101, 150 ].indexOf( error.data ) !== -1 ) {
                media.error = {
                  customCode: error.data
                };
                media.dispatchEvent( "error" );
              }
            }
          }
        });
  
        var volumeupdate = function() {
  
          if ( options.destroyed ) {
  
            return;
          }
  
          if ( lastMuted !== options.youtubeObject.isMuted() ) {
  
            lastMuted = options.youtubeObject.isMuted();
            media.dispatchEvent( "volumechange" );
          }
  
          if ( lastVolume !== options.youtubeObject.getVolume() ) {
  
            lastVolume = options.youtubeObject.getVolume();
            media.dispatchEvent( "volumechange" );
          }
  
          setTimeout( volumeupdate, 250 );
        };
      };
  
      if ( onYouTubePlayerAPIReady.ready ) {
  
        youtubeInit();
      } else {
  
        onYouTubePlayerAPIReady.waiting.push( youtubeInit );
      }
    },
    _teardown: function( options ) {
  
      options.destroyed = true;
  
      var youtubeObject = options.youtubeObject;
      if( youtubeObject ){
        youtubeObject.stopVideo();
        youtubeObject.clearVideo();
      }
  
      this.removeChild( document.getElementById( options._container.id ) );
    }
  });
  
  (function() {
  
    var scriptLoaded = false,
        loading = false;
    Popcorn.player( "soundcloud", {
      _canPlayType: function( nodeName, url ) {
  
        return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(soundcloud)/).test( url ) && nodeName.toLowerCase() !== "video";
      },
      _setup: function( options ) {
  
        var media = this,
            container = document.createElement( "iframe" ),
            lastVolume = 1,
            currentTime = 0,
            paused = true,
            realPaused = true,
            widget,
            duration = 0,
            muted = false,
            playerQueue = Popcorn.player.playerQueue();
  
        options._container = container;
        media.style.visibility = "hidden";
  
        media.play = function() {
  
          paused = false;
          playerQueue.add(function() {
  
            if ( realPaused ) {
  
              widget && widget.play();
            } else {
              playerQueue.next();
            }
          });
        };
  
        media.pause = function() {
  
          paused = true;
  
          playerQueue.add(function() {
  
            if ( !realPaused ) {
  
              widget && widget.pause();
            } else {
              playerQueue.next();
            }
          });
        };
  
        // getter and setter for muted property, multiply volume by 100 as that is the scale soundcloud works on
        Object.defineProperties( media, {
          muted: {
            set: function( val ) {
              if ( val ) {
                widget && widget.getVolume(function( data ) {
                  lastVolume = data / 100;
                });
                widget && widget.setVolume( 0 );
                muted = true;
              } else {
                widget && widget.setVolume( lastVolume * 100 );
                muted = false;
              }
              media.dispatchEvent( "volumechange" );
            },
            get: function() {
              return muted;
            }
          },
          volume: {
            set: function( val ) {
              widget && widget.setVolume( val * 100 );
              lastVolume = val ;
              media.dispatchEvent( "volumechange" );
            },
            get: function() {
              return muted ? 0 : lastVolume;
            }
          },
          currentTime: {
            set: function( val ) {
              currentTime = val;
              widget && widget.seekTo( val * 1000 );
              media.dispatchEvent( "seeked" );
              media.dispatchEvent( "timeupdate" );
            },
            get: function() {
              return currentTime;
            }
          },
          duration: {
            get: function() {
              return duration;
            }
          },
          paused: {
            get: function() {
              return paused;
            }
          }
        });
        // called when the SoundCloud api script has loaded
        function scriptReady() {
          scriptLoaded = true;
  
          SC.initialize({
            client_id: "PRaNFlda6Bhf5utPjUsptg"
          });
  
          SC.get( "/resolve", {
            url: media.src
          }, function( data ) {
            media.width = media.style.width ? "" + media.offsetWidth : "560";
            media.height = media.style.height ? "" + media.offsetHeight : "315";
            // TODO: There are quite a few options here that we should pass on to the user
            container.scrolling = "no";
            container.frameborder = "no";
            container.id = "soundcloud-" + Popcorn.guid();
            container.src = "http://w.soundcloud.com/player/?url=" + data.uri +
            "&show_artwork=false" +
            "&buying=false" +
            "&liking=false" +
            "&sharing=false";
  
            container.width = "100%";
            container.height = "100%";
  
            options.loadListener = function( e ) {
              options.widget = widget = SC.Widget( container.id );
              // setup all of our listeners
              widget.bind(SC.Widget.Events.FINISH, function() {
                media.pause();
  
                media.dispatchEvent( "ended" );
              });
  
              widget.bind(SC.Widget.Events.PLAY_PROGRESS, function( data ) {
  
                currentTime = data.currentPosition / 1000;
                media.dispatchEvent( "timeupdate" );
              });
  
              widget.bind(SC.Widget.Events.PLAY, function( data ) {
  
                paused = realPaused = false;
  
                media.dispatchEvent( "play" );
                media.dispatchEvent( "playing" );
                media.currentTime = currentTime;
  
                playerQueue.next();
              });
  
              widget.bind(SC.Widget.Events.PAUSE, function( data ) {
  
                paused = realPaused = true;
                media.dispatchEvent( "pause" );
  
                playerQueue.next();
              });
              widget.bind(SC.Widget.Events.READY, function( data ) {
                widget.getDuration(function( data ) {
  
                  duration = data / 1000;
  
                  media.style.visibility = "visible";
                  media.dispatchEvent( "durationchange" );
                  // update the readyState after we have the duration
                  media.readyState = 4;
                  media.dispatchEvent( "readystatechange" );
                  media.dispatchEvent( "loadedmetadata" );
                  media.dispatchEvent( "loadeddata" );
                  media.dispatchEvent( "canplaythrough" );
                  media.dispatchEvent( "load" );
                  !media.paused && media.play();
                });
                widget.getVolume(function( data ) {
                  lastVolume = data / 100;
                });
              });
            };
  
            container.addEventListener( "load", options.loadListener, false );
            media.appendChild( container );
          });
        }
  
        // load the SoundCloud API script if it doesn't exist
        function loadScript() {
          if ( !loading ) {
            loading = true;
            Popcorn.getScript( "http://w.soundcloud.com/player/api.js", function() {
              Popcorn.getScript( "http://connect.soundcloud.com/sdk.js", function() {
                scriptReady();
              });
            });
          } else {
            (function isReady() {
              setTimeout(function() {
                if ( !scriptLoaded ) {
                  isReady();
                } else {
                  scriptReady();
                }
              }, 100 );
            })();
          }
        }
  
        if ( !scriptLoaded ) {
          loadScript();
        } else {
          scriptReady();
        }
      },
      _teardown: function( options ) {
        var widget = options.widget,
            events = SC.Widget.Events,
            container = options._container,
            parentContainer = container.parentNode;
  
        options.destroyed = true;
  
        // if the widget never got setup, remove the containers load listener and return
        if ( !widget ) {
          container.removeEventListener( "load", options.loadEventListener, false );
          return;
        }
  
        // remove all bound soundcloud listeners
        for ( var prop in events ) {
          widget && widget.unbind( events[ prop ] );
        }
      }
    });
  })();
  
  (function() {
  
    // global callback for vimeo.. yuck.
    vimeo_player_loaded = function( playerId ) {
      vimeo_player_loaded[ playerId ] && vimeo_player_loaded[ playerId ]();
    };
    vimeo_player_loaded.seek = {};
    vimeo_player_loaded.loadProgress = {};
    vimeo_player_loaded.play = {};
    vimeo_player_loaded.pause = {};
  
    Popcorn.player( "vimeo", {
      _canPlayType: function( nodeName, url ) {
  
        return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(vimeo)/).test( url ) && nodeName.toLowerCase() !== "video";
      },
      _setup: function( options ) {
  
        var media = this,
            vimeoObject,
            vimeoContainer = document.createElement( "div" ),
            currentTime = 0,
            paused = true,
            seekTime = 0,
            seeking = false,
            volumeChanged = false,
            lastMuted = false,
            lastVolume = 0,
            height,
            width,
            playerQueue = Popcorn.player.playerQueue();
  
        vimeoContainer.id = media.id + Popcorn.guid();
  
        media.appendChild( vimeoContainer );
  
        // setting vimeo player's height and width, default to 560 x 315
        width = media.style.width ? "" + media.offsetWidth : "560";
        height = media.style.height ? "" + media.offsetHeight : "315";
  
        var vimeoInit = function() {
  
          var flashvars,
              params,
              attributes = {},
              src = media.src,
              toggleMuteVolume = 0,
              loadStarted = false;
  
          vimeo_player_loaded[ vimeoContainer.id ] = function() {
  
            vimeoObject = document.getElementById( vimeoContainer.id );
  
            vimeo_player_loaded.seek[ vimeoContainer.id ] = function( time ) {
              if( time.seconds !== currentTime ) {
                seeking = true;
                media.dispatchEvent( "seeking" );
                currentTime = time.seconds;
                seeking = false;
                media.dispatchEvent( "timeupdate" );
                media.dispatchEvent( "seeked" );
              }
            };
  
            vimeo_player_loaded.play[ vimeoContainer.id ] = function() {
  
              paused = false;
              media.dispatchEvent( "play" );
              media.dispatchEvent( "playing" );
              timeUpdate();
  
              playerQueue.next();
            };
  
            vimeo_player_loaded.pause[ vimeoContainer.id ] = function() {
  
              paused = true;
              media.dispatchEvent( "pause" );
  
              playerQueue.next();
            };
  
            vimeo_player_loaded.loadProgress[ vimeoContainer.id ] = function( progress ) {
  
              if ( !loadStarted ) {
                loadStarted = true;
                media.dispatchEvent( "loadstart" );
              }
  
              if ( progress.percent === 100 ) {
                media.dispatchEvent( "canplaythrough" );
              }
            };
  
            vimeoObject.api_addEventListener( "seek", "vimeo_player_loaded.seek." + vimeoContainer.id );
            vimeoObject.api_addEventListener( "loadProgress", "vimeo_player_loaded.loadProgress." + vimeoContainer.id );
            vimeoObject.api_addEventListener( "play", "vimeo_player_loaded.play." + vimeoContainer.id );
            vimeoObject.api_addEventListener( "pause", "vimeo_player_loaded.pause." + vimeoContainer.id );
  
            var timeUpdate = function() {
              if ( !media.paused ) {
                currentTime = vimeoObject.api_getCurrentTime();
                media.dispatchEvent( "timeupdate" );
                setTimeout( timeUpdate, 10 );
              }
            },
  
            isMuted = function() {
  
              return vimeoObject.api_getVolume() === 0;
            };
  
            var volumeUpdate = function() {
  
              var muted = isMuted(),
              vol = vimeoObject.api_getVolume();
              if ( lastMuted !== muted ) {
                lastMuted = muted;
                media.dispatchEvent( "volumechange" );
              }
  
              if ( lastVolume !== vol ) {
                lastVolume = vol;
                media.dispatchEvent( "volumechange" );
              }
  
              setTimeout( volumeUpdate, 250 );
            };
  
            media.play = function() {
  
  
              paused = false;
              playerQueue.add(function() {
  
                if ( vimeoObject.api_paused() ) {
  
                  vimeoObject.api_play();
                } else {
                  playerQueue.next();
                }
              });
            };
  
            Popcorn.player.defineProperty( media, "seeking", {
              get: function() {
                return seeking;
              }
            });
  
            media.pause = function() {
  
              paused = true;
              playerQueue.add(function() {
  
                if ( !vimeoObject.api_paused() ) {
  
                  vimeoObject.api_pause();
                } else {
                  playerQueue.next();
                }
              });
            };
  
            Popcorn.player.defineProperty( media, "currentTime", {
  
              set: function( val ) {
  
                if ( !val ) {
                  return currentTime;
                }
  
                vimeoObject.api_seekTo( +val );
  
                return currentTime;
              },
  
              get: function() {
  
                return currentTime;
              }
            });
  
            Popcorn.player.defineProperty( media, "paused", {
  
              get: function() {
  
                return paused;
              }
            });
  
            Popcorn.player.defineProperty( media, "muted", {
  
              set: function( val ) {
  
                if ( isMuted() !== val ) {
  
                  if ( val ) {
                    toggleMuteVolume = vimeoObject.api_getVolume();
                    vimeoObject.api_setVolume( 0 );
                  } else {
  
                    vimeoObject.api_setVolume( toggleMuteVolume );
                  }
                }
              },
              get: function() {
  
                return isMuted();
              }
            });
  
            Popcorn.player.defineProperty( media, "volume", {
  
              set: function( val ) {
  
                if ( !val || typeof val !== "number" || ( val < 0 || val > 1 ) ) {
                  return vimeoObject.api_getVolume() / 100;
                }
  
                if ( vimeoObject.api_getVolume() !== val ) {
                  vimeoObject.api_setVolume( val * 100 );
                  lastVolume = vimeoObject.api_getVolume();
                  media.dispatchEvent( "volumechange" );
                }
  
                return vimeoObject.api_getVolume() / 100;
              },
              get: function() {
  
                return vimeoObject.api_getVolume() / 100;
              }
            });
  
            media.duration = vimeoObject.api_getDuration();
            media.dispatchEvent( "durationchange" );
            media.dispatchEvent( "loadedmetadata" );
            media.dispatchEvent( "loadeddata" );
            volumeUpdate();
            media.readyState = 4;
            media.dispatchEvent( "canplaythrough" );
          };
  
          var clip_id = ( /\d+$/ ).exec( src );
  
          flashvars = {
            // Load a video not found poster if the url does not contain a valid id
            clip_id: clip_id ? clip_id[ 0 ] : 0,
            api: 1,
            js_swf_id: vimeoContainer.id
          };
  
          //  extend options from user to flashvars. NOTE: Videos owned by Plus Vimeo users may override these options
          Popcorn.extend( flashvars, options );
  
          params = {
            allowscriptaccess: "always",
            allowfullscreen: "true",
            wmode: "transparent"
          };
  
          swfobject.embedSWF( "//vimeo.com/moogaloop.swf", vimeoContainer.id,
                              width, height, "9.0.0", "expressInstall.swf",
                              flashvars, params, attributes );
  
        };
  
        if ( !window.swfobject ) {
  
          Popcorn.getScript( "//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", vimeoInit );
        } else {
  
          vimeoInit();
        }
      }
    });
  })();
  