diff --git a/.gitignore b/.gitignore index be262a8b4..a029f5e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ .DS_Store -dist +dist/popcorn-ie8* +dist/popcorn.modules* +dist/popcorn.players* +dist/popcorn.parsers* +dist/popcorn.plugins* +dist/popcorn.effects* +dist/popcorn.js *~ \.\#* \#*\# \ No newline at end of file diff --git a/Makefile b/Makefile index e66c1b0a4..1eb1606bd 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,16 @@ POPCORN_COMPLETE_LIST := --js ${POPCORN_SRC} \ POPCORN_COMPLETE_DIST = ${DIST_DIR}/popcorn-complete.js POPCORN_COMPLETE_MIN = ${DIST_DIR}/popcorn-complete.min.js +# popcorn - plugins +POPCORN_WRAPPERS_LIST := --js ${POPCORN_SRC} \ + $(shell for js in ${MODULES_SRC} ; do echo --js $$js ; done) \ + $(shell for js in ${WRAPPERS_SRC} ; do echo --js $$js ; done) \ + $(shell for js in ${EFFECTS_SRC} ; do echo --js $$js ; done) \ + $(shell for js in ${PARSERS_SRC} ; do echo --js $$js ; done) \ + $(shell for js in ${PLAYERS_SRC} ; do echo --js $$js ; done) +POPCORN_WRAPPERS_DIST = ${DIST_DIR}/popcorn-wrappers.js +POPCORN_WRAPPERS_MIN = ${DIST_DIR}/popcorn-wrappers.min.js + # For IE8 compat we include a subset of all files, known to work with IE8. POPCORN_IE8_FILES := \ $(IE8_DIR)/popcorn.ie8.js \ @@ -123,7 +133,7 @@ add_version = cat $(1) | sed -e 's/@VERSION/${VERSION}/' > $(1).__tmp__ ; \ # Run the file through jslint run_lint = @@$(RHINO) build/jslint-check.js $(1) -all: setup popcorn modules wrappers plugins parsers players effects complete min ie8 +all: setup popcorn modules wrappers plugins parsers players effects complete min ie8 wrappers-complete @@echo "Popcorn build complete. To create a testing mirror, run: make testing." check: lint lint-plugins lint-parsers lint-players lint-effects lint-modules lint-wrappers @@ -139,7 +149,7 @@ ${POPCORN_DIST}: $(POPCORN_SRC) | $(DIST_DIR) @@$(call add_license, $(POPCORN_DIST)) @@$(call add_version, $(POPCORN_DIST)) -min: setup ${POPCORN_MIN} ${MODULES_MIN} $(WRAPPERS_MIN) ${PLUGINS_MIN} ${PARSERS_MIN} ${PLAYERS_MIN} $(EFFECTS_MIN) ${POPCORN_COMPLETE_MIN} +min: setup ${POPCORN_MIN} ${MODULES_MIN} $(WRAPPERS_MIN) ${PLUGINS_MIN} ${PARSERS_MIN} ${PLAYERS_MIN} $(EFFECTS_MIN) ${POPCORN_COMPLETE_MIN} ${POPCORN_WRAPPERS_MIN} ${POPCORN_MIN}: ${POPCORN_DIST} @@echo "Building" ${POPCORN_MIN} @@ -153,6 +163,12 @@ ${POPCORN_COMPLETE_MIN}: ${POPCORN_SRC} ${MODULES_SRC} ${PLUGINS_SRC} ${PARSERS_ @@$(call add_license, $(POPCORN_COMPLETE_MIN)) @@$(call add_version, $(POPCORN_COMPLETE_MIN)) +${POPCORN_WRAPPERS_MIN}: ${POPCORN_SRC} ${MODULES_SRC} ${PLUGINS_SRC} ${PARSERS_SRC} $(EFFECTS_SRC) ${DIST_DIR} + @@echo "Building" ${POPCORN_WRAPPERS_MIN} + @@$(call compile, $(POPCORN_WRAPPERS_LIST), $(POPCORN_WRAPPERS_MIN)) + @@$(call add_license, $(POPCORN_WRAPPERS_MIN)) + @@$(call add_version, $(POPCORN_WRAPPERS_MIN)) + modules: setup ${MODULES_DIST} ${MODULES_MIN}: ${MODULES_DIST} @@ -219,6 +235,12 @@ complete: setup ${POPCORN_SRC} ${MODULES_SRC} ${WRAPPERS_SRC} ${PARSERS_SRC} ${P @@$(call add_license, $(POPCORN_COMPLETE_DIST)) @@$(call add_version, $(POPCORN_COMPLETE_DIST)) +wrappers-complete: + @@echo "Building popcorn + modules + wrappers + plugins + parsers + players + effects..." + @@cat ${POPCORN_SRC} ${MODULES_SRC} ${WRAPPERS_SRC} ${PARSERS_SRC} ${PLAYERS_SRC} $(EFFECTS_SRC) > $(POPCORN_WRAPPERS_DIST) + @@$(call add_license, $(POPCORN_WRAPPERS_DIST)) + @@$(call add_version, $(POPCORN_WRAPPERS_DIST)) + ie8: $(POPCORN_IE8_MIN) $(POPCORN_IE8_MIN): $(POPCORN_IE8_DIST) diff --git a/dist/popcorn-complete.js b/dist/popcorn-complete.js new file mode 100644 index 000000000..8a932c970 --- /dev/null +++ b/dist/popcorn-complete.js @@ -0,0 +1,11424 @@ +/* + * popcorn.js version d05323f + * http://popcornjs.org + * + * Copyright 2011, Mozilla Foundation + * Licensed under the MIT license + */ + +(function(global, document) { + + // Popcorn.js does not support archaic browsers + if ( !document.addEventListener ) { + global.Popcorn = { + isSupported: false + }; + + var methods = ( "byId 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 ); + }, + + Abstract = { + // [[Put]] props from dictionary onto |this| + // MUST BE CALLED FROM WITHIN A CONSTRUCTOR: + // Abstract.put.call( this, dictionary ); + put: function( dictionary ) { + // For each own property of src, let key be the property key + // and desc be the property descriptor of the property. + for ( var key in dictionary ) { + if ( dictionary.hasOwnProperty( key ) ) { + this[ key ] = dictionary[ key ]; + } + } + } + }, + + + // 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 = "d05323f"; + + // 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, nodeName, + 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; + + // inner reference to this media element's nodeName string value + nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video"; + + // Create an audio or video element property reference + this[ nodeName ] = this.media; + + this.options = Popcorn.extend( {}, options ) || {}; + + // Resolve custom ID or default prefixed ID + this.id = this.options.id || Popcorn.guid( nodeName ); + + // Throw if an attempt is made to use an ID that already exists + if ( Popcorn.byId( this.id ) ) { + throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" ); + } + + 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: new TrackEvents( this ) + }; + + // Register new instance + Popcorn.instances.push( this ); + + // 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( "loadedmetadata", 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.isDestroyed ) { + self.data.durationChange = function() { + var newDuration = self.media.duration, + newDurationPlus = newDuration + 1, + byStart = self.data.trackEvents.byStart, + byEnd = self.data.trackEvents.byEnd; + + // Remove old padding events + byStart.pop(); + byEnd.pop(); + + // Remove any internal tracking of events that have end times greater than duration + // otherwise their end events will never be hit. + for ( var k = byEnd.length - 1; k > 0; k-- ) { + if ( byEnd[ k ].end > newDuration ) { + self.removeTrackEvent( byEnd[ k ]._id ); + } + } + + // Remove any internal tracking of events that have end times greater than duration + // otherwise their end events will never be hit. + for ( var i = 0; i < byStart.length; i++ ) { + if ( byStart[ i ].end > newDuration ) { + self.removeTrackEvent( byStart[ i ]._id ); + } + } + + // References to byEnd/byStart are reset, so accessing it this way is + // forced upon us. + self.data.trackEvents.byEnd.push({ + start: newDurationPlus, + end: newDurationPlus + }); + + self.data.trackEvents.byStart.push({ + start: newDurationPlus, + end: newDurationPlus + }); + }; + + // Listen for duration changes and adjust internal tracking of event timings + self.media.addEventListener( "durationchange", self.data.durationChange, false ); + } + + 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 ); + } + } + }; + + self.media.addEventListener( "error", function() { + self.error = self.media.error; + }, false ); + + // http://www.whatwg.org/specs/web-apps/current-work/#dom-media-readystate + // + // If media is in readyState (rS) >= 1, we know the media's duration, + // which is required before running the isReady function. + // If rS is 0, attach a listener for "loadedmetadata", + // ( Which indicates that the media has moved from rS 0 to 1 ) + // + // This has been changed from a check for rS 2 because + // in certain conditions, Firefox can enter this code after dropping + // to rS 1 from a higher state such as 2 or 3. This caused a "loadeddata" + // listener to be attached to the media object, an event that had + // already triggered and would not trigger again. This left Popcorn with an + // instance that could never start a timeUpdate loop. + if ( self.media.readyState >= 1 ) { + + isReady(); + } else { + + self.media.addEventListener( "loadedmetadata", isReady, false ); + } + + return this; + } + }; + + // Extend constructor prototype to instance prototype + // Allows chaining methods to instances + Popcorn.p.init.prototype = Popcorn.p; + + Popcorn.byId = function( str ) { + var instances = Popcorn.instances, + length = instances.length, + i = 0; + + for ( ; i < length; i++ ) { + if ( instances[ i ].id === str ) { + return instances[ i ]; + } + } + + return null; + }; + + 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 ) { + + if ( !elem.parentNode ) { + return null; + } + + 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 ] ) { + return; + } + + instance.data.disabled[ plugin ] = true; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + 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 ); + + instance.emit( "trackend", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackend" + }) + ); + } + } + + return instance; + }, + enable: function( instance, plugin ) { + + if ( !instance.data.disabled[ plugin ] ) { + return; + } + + instance.data.disabled[ plugin ] = false; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + 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 ); + + instance.emit( "trackstart", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackstart", + track: event + }) + ); + } + } + + return instance; + }, + destroy: function( instance ) { + var events = instance.data.events, + trackEvents = instance.data.trackEvents, + 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 ); + } + + // Remove all data.trackEvents #1178 + trackEvents.byStart.length = 0; + trackEvents.byEnd.length = 0; + + if ( !instance.isDestroyed ) { + instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); + instance.isDestroyed = true; + } + + Popcorn.instances.splice( Popcorn.instances.indexOf( instance ), 1 ); + } + }); + + // 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 Math.round( this.media.currentTime ); + }, + + // Attach an event to a single point in time + exec: function( id, time, fn ) { + var length = arguments.length, + eventType = "trackadded", + trackEvent, sec, options; + + // Check if first could possibly be a SMPTE string + // p.cue( "smpte string", fn ); + // try/catch avoid awful throw in Popcorn.util.toSeconds + // TODO: Get rid of that, replace with NaN return? + try { + sec = Popcorn.util.toSeconds( id ); + } catch ( e ) {} + + // If it can be converted into a number then + // it's safe to assume that the string was SMPTE + if ( typeof sec === "number" ) { + id = sec; + } + + // Shift arguments based on use case + // + // Back compat for: + // p.cue( time, fn ); + if ( typeof id === "number" && length === 2 ) { + fn = time; + time = id; + id = Popcorn.guid( "cue" ); + } else { + // Support for new forms + + // p.cue( "empty-cue" ); + if ( length === 1 ) { + // Set a time for an empty cue. It's not important what + // the time actually is, because the cue is a no-op + time = -1; + + } else { + + // Get the TrackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + if ( trackEvent ) { + + // remove existing cue so a new one can be added via trackEvents.add + this.data.trackEvents.remove( id ); + TrackEvent.end( this, trackEvent ); + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + eventType = "cuechange"; + + // p.cue( "my-id", 12 ); + // p.cue( "my-id", function() { ... }); + if ( typeof id === "string" && length === 2 ) { + + // p.cue( "my-id", 12 ); + // The path will update the cue time. + if ( typeof time === "number" ) { + // Re-use existing TrackEvent start callback + fn = trackEvent._natives.start; + } + + // p.cue( "my-id", function() { ... }); + // The path will update the cue function + if ( typeof time === "function" ) { + fn = time; + // Re-use existing TrackEvent start time + time = trackEvent.start; + } + } + } else { + + if ( length >= 2 ) { + + // p.cue( "a", "00:00:00"); + if ( typeof time === "string" ) { + try { + sec = Popcorn.util.toSeconds( time ); + } catch ( e ) {} + + time = sec; + } + + // p.cue( "b", 11 ); + // p.cue( "b", 11, function() {} ); + if ( typeof time === "number" ) { + fn = fn || Popcorn.nop(); + } + + // p.cue( "c", function() {}); + if ( typeof time === "function" ) { + fn = time; + time = -1; + } + } + } + } + } + + options = { + id: id, + start: time, + end: time + 1, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "cue" + } + }; + + if ( trackEvent ) { + options = Popcorn.extend( trackEvent, options ); + } + + if ( eventType === "cuechange" ) { + + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, options ); + + this.emit( eventType, Popcorn.extend({}, options, { + id: id, + type: eventType, + previousValue: { + time: trackEvent.start, + fn: trackEvent._natives.start + }, + currentValue: { + time: time, + fn: fn || Popcorn.nop + }, + track: trackEvent + })); + } else { + // Creating a one second track event with an empty end + Popcorn.addTrackEvent( this, options ); + } + + 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, clonedEvents, + events = this.data.events[ type ]; + + // setup checks for custom event system + if ( events ) { + eventInterface = Popcorn.events.getInterface( type ); + + if ( eventInterface ) { + evt = document.createEvent( eventInterface ); + evt.initEvent( type, true, true, global, 1 ); + + this.media.dispatchEvent( evt ); + + return this; + } + + // clone events in case callbacks remove callbacks themselves + clonedEvents = events.slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( this, data ); + } + } + + return this; + }, + listen: function( type, fn ) { + var self = this, + hasEvents = true, + eventHook = Popcorn.events.hooks[ type ], + origType = type, + clonedEvents, + tmp; + + if ( typeof fn !== "function" ) { + throw new Error( "Popcorn.js Error: Listener is not a function" ); + } + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = []; + // Toggle if the previous assumption was untrue + 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 ].push( fn ); + + // only attach one event of any type + if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { + this.media.addEventListener( type, function( event ) { + if ( self.data.events[ type ] ) { + // clone events in case callbacks remove callbacks themselves + clonedEvents = self.data.events[ type ].slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( self, event ); + } + } + }, false ); + } + return this; + }, + unlisten: function( type, fn ) { + var ind, + events = this.data.events[ type ]; + + if ( !events ) { + return; // no listeners = nothing to do + } + + if ( typeof fn === "string" ) { + // legacy support for string-based removal -- not recommended + for ( var i = 0; i < events.length; i++ ) { + if ( events[ i ].name === fn ) { + // decrement i because array length just got smaller + events.splice( i--, 1 ); + } + } + + return this; + } else if ( typeof fn === "function" ) { + while( ind !== -1 ) { + ind = events.indexOf( fn ); + if ( ind !== -1 ) { + events.splice( ind, 1 ); + } + } + + return this; + } + + // if we got to this point, we are deleting all functions of this type + this.data.events[ type ] = null; + + return this; + } + }, + hooks: { + canplayall: { + bind: "canplaythrough", + add: function( event, callback ) { + + var state = false; + + if ( this.media.readyState ) { + + // always call canplayall asynchronously + setTimeout(function() { + callback.call( this, event ); + }.bind(this), 0 ); + + 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 - construct simple "TrackEvent" + // data type objects + function TrackEvent( track ) { + Abstract.put.call( this, track ); + } + + // Determine if a TrackEvent's "start" and "trackstart" must be called. + TrackEvent.start = function( instance, track ) { + + if ( track.end > instance.media.currentTime && + track.start <= instance.media.currentTime && !track._running ) { + + track._running = true; + instance.data.running[ track._natives.type ].push( track ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.start.call( instance, null, track ); + + instance.emit( "trackstart", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackstart", + track: track + }) + ); + } + } + }; + + // Determine if a TrackEvent's "end" and "trackend" must be called. + TrackEvent.end = function( instance, track ) { + + var runningPlugins; + + if ( ( track.end <= instance.media.currentTime || + track.start > instance.media.currentTime ) && track._running ) { + + runningPlugins = instance.data.running[ track._natives.type ]; + + track._running = false; + runningPlugins.splice( runningPlugins.indexOf( track ), 1 ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.end.call( instance, null, track ); + + instance.emit( "trackend", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackend", + track: track + }) + ); + } + } + }; + + // Internal Only - construct "TrackEvents" + // data type objects that are used by the Popcorn + // instance, stored at p.data.trackEvents + function TrackEvents( parent ) { + this.parent = parent; + + this.byStart = [{ + start: -1, + end: -1 + }]; + + this.byEnd = [{ + start: -1, + end: -1 + }]; + this.animating = []; + this.startIndex = 0; + this.endIndex = 0; + this.previousUpdateTime = -1; + + this.count = 1; + } + + function isMatch( obj, key, value ) { + return obj[ key ] && obj[ key ] === value; + } + + TrackEvents.prototype.where = function( params ) { + return ( this.parent.getTrackEvents() || [] ).filter(function( event ) { + var key, value; + + // If no explicit params, match all TrackEvents + if ( !params ) { + return true; + } + + // Filter keys in params against both the top level properties + // and the _natives properties + for ( key in params ) { + value = params[ key ]; + if ( isMatch( event, key, value ) || isMatch( event._natives, key, value ) ) { + return true; + } + } + return false; + }); + }; + + TrackEvents.prototype.add = function( track ) { + + // Store this definition in an array sorted by times + var byStart = this.byStart, + byEnd = this.byEnd, + startIndex, endIndex; + + // Push track event ids into the history + if ( track && track._id ) { + this.parent.data.history.push( track._id ); + } + + track.start = Popcorn.util.toSeconds( track.start, this.parent.options.framerate ); + track.end = Popcorn.util.toSeconds( track.end, this.parent.options.framerate ); + + 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; + } + } + + // update startIndex and endIndex + if ( startIndex <= this.parent.data.trackEvents.startIndex && + track.start <= this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.startIndex++; + } + + if ( endIndex <= this.parent.data.trackEvents.endIndex && + track.end < this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.endIndex++; + } + + this.count++; + + }; + + TrackEvents.prototype.remove = function( removeId, state ) { + + if ( removeId instanceof TrackEvent ) { + removeId = removeId.id; + } + + if ( typeof removeId === "object" ) { + // Filter by key=val and remove all matching TrackEvents + this.where( removeId ).forEach(function( event ) { + // |this| refers to the calling Popcorn "parent" instance + this.removeTrackEvent( event._id ); + }, this.parent ); + + return this; + } + + var start, end, animate, historyLen, track, + length = this.byStart.length, + index = 0, + indexWasAt = 0, + byStart = [], + byEnd = [], + animating = [], + history = [], + comparable = {}; + + state = state || {}; + + while ( --length > -1 ) { + start = this.byStart[ index ]; + end = this.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; + + // cache the track event being removed + track = 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 = this.animating.length; + index = 0; + + if ( length ) { + while ( --length > -1 ) { + animate = this.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 <= this.startIndex ) { + this.startIndex--; + } + + if ( indexWasAt <= this.endIndex ) { + this.endIndex--; + } + + this.byStart = byStart; + this.byEnd = byEnd; + this.animating = animating; + this.count--; + + historyLen = this.parent.data.history.length; + + for ( var i = 0; i < historyLen; i++ ) { + if ( this.parent.data.history[ i ] !== removeId ) { + history.push( this.parent.data.history[ i ] ); + } + } + + // Update ordered history array + this.parent.data.history = history; + + }; + + // Helper function used to retrieve old values of properties that + // are provided for update. + function getPreviousProperties( oldOptions, newOptions ) { + var matchProps = {}; + + for ( var prop in oldOptions ) { + if ( hasOwn.call( newOptions, prop ) && hasOwn.call( oldOptions, prop ) ) { + matchProps[ prop ] = oldOptions[ prop ]; + } + } + + return matchProps; + } + + // Internal Only - Adds track events to the instance object + Popcorn.addTrackEvent = function( obj, track ) { + var temp; + + if ( track instanceof TrackEvent ) { + return; + } + + track = new TrackEvent( 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 ] ) ) { + + // To ensure that the TrackEvent Invariant Policy is enforced, + // First, copy the properties of the newly created track event event + // to a temporary holder + temp = Popcorn.extend( {}, track ); + + // Next, copy the default onto the newly created trackevent, followed by the + // temporary holder. + Popcorn.extend( track, obj.options.defaults[ track._natives.type ], temp ); + } + + if ( track._natives ) { + // Supports user defined track event id + track._id = track.id || track._id || Popcorn.guid( track._natives.type ); + + // Trigger _setup method if exists + if ( track._natives._setup ) { + + track._natives._setup.call( obj, track ); + + obj.emit( "tracksetup", Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "tracksetup", + track: track + })); + } + } + + obj.data.trackEvents.add( track ); + TrackEvent.start( obj, track ); + + this.timeUpdate( obj, null, true ); + + // Store references to user added trackevents in ref table + if ( track._id ) { + Popcorn.addTrackEvent.ref( obj, track ); + } + + obj.emit( "trackadded", Popcorn.extend({}, track, + track._natives ? { plugin: track._natives.type } : {}, { + type: "trackadded", + track: 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 track = obj.getTrackEvent( removeId ); + + if ( !track ) { + return; + } + + // If a _teardown function was defined, + // enforce for track event removals + if ( track._natives._teardown ) { + track._natives._teardown.call( obj, track ); + } + + obj.data.trackEvents.remove( removeId ); + + // Update track event references + Popcorn.removeTrackEvent.ref( obj, removeId ); + + if ( track._natives ) { + + // Fire a trackremoved event + obj.emit( "trackremoved", Popcorn.extend({}, track, { + plugin: track._natives.type, + type: "trackremoved", + track: track + })); + } + }; + + // 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, + track: byEnd + }) + ); + } + } + + end++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + if ( byStart.end > currentTime && + byStart._running === false ) { + + 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, + track: byStart + }) + ); + } + } + 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, + track: byStart + }) + ); + } + } + start--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd.start <= currentTime && + byEnd._running === false ) { + + 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, + track: byEnd + }) + ); + } + } + 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 isfn = typeof definition === "function", + blacklist = [ "start", "end", "type", "manifest" ], + methods = [ "_setup", "_teardown", "start", "end", "frame" ], + plugin = {}, + setup; + + // 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 = options._natives.plugin = 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 ); + + args[ 1 ]._running = false; + this.emit( "trackend", + Popcorn.extend( {}, options, { + plugin: natives.type, + type: "trackend", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + }) + ); + }, natives._teardown ); + + // extend teardown to always trigger trackteardown after teardown + natives._teardown = combineFn( natives._teardown, function() { + + this.emit( "trackteardown", Popcorn.extend( {}, options, { + plugin: name, + type: "trackteardown", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + })); + }); + + // default to an empty string if no effect exists + // split string into an array of effects + options.compose = options.compose || []; + if ( typeof options.compose === "string" ) { + options.compose = options.compose.split( " " ); + } + options.effect = options.effect || []; + if ( typeof options.effect === "string" ) { + 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; + } + + if ( !options._id && options._natives ) { + // ensure an initial id is there before setup is called + options._id = Popcorn.guid( options._natives.type ); + } + + if ( options instanceof TrackEvent ) { + + if ( options._natives ) { + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + // Trigger _setup method if exists + if ( options._natives._setup ) { + + options._natives._setup.call( this, options ); + + this.emit( "tracksetup", Popcorn.extend( {}, options, { + plugin: options._natives.type, + type: "tracksetup", + track: options + })); + } + } + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + if ( options._id ) { + Popcorn.addTrackEvent.ref( this, options ); + } + } else { + // Create new track event for this instance + Popcorn.addTrackEvent( this, options ); + } + + // Future support for plugin event definitions + // for all of the native events + Popcorn.forEach( setup, function( callback, type ) { + // Don't attempt to create events for certain properties: + // "start", "end", "type", "manifest". Fixes #1365 + if ( blacklist.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( id, options ) { + var length = arguments.length, + trackEvent, defaults, mergedSetupOpts, previousOpts, newOpts; + + // Shift arguments based on use case + // + // Back compat for: + // p.plugin( options ); + if ( id && !options ) { + options = id; + id = null; + } else { + + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + // If the track event does not exist, ensure that the options + // object has a proper id + if ( !trackEvent ) { + options.id = id; + + // If the track event does exist, merge the updated properties + } else { + + newOpts = options; + previousOpts = getPreviousProperties( trackEvent, newOpts ); + + // Call the plugins defined update method if provided. Allows for + // custom defined updating for a track event to be defined by the plugin author + if ( trackEvent._natives._update ) { + + this.data.trackEvents.remove( trackEvent ); + + // It's safe to say that the intent of Start/End will never change + // Update them first before calling update + if ( hasOwn.call( options, "start" ) ) { + trackEvent.start = options.start; + } + + if ( hasOwn.call( options, "end" ) ) { + trackEvent.end = options.end; + } + + TrackEvent.end( this, trackEvent ); + + if ( isfn ) { + definition.call( this, trackEvent ); + } + + trackEvent._natives._update.call( this, trackEvent, options ); + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + } else { + // This branch is taken when there is no explicitly defined + // _update method for a plugin. Which will occur either explicitly or + // as a result of the plugin definition being a function that _returns_ + // a definition object. + // + // In either case, this path can ONLY be reached for TrackEvents that + // already exist. + + // Directly update the TrackEvent instance. + // This supports TrackEvent invariant enforcement. + Popcorn.extend( trackEvent, options ); + + this.data.trackEvents.remove( id ); + + // If a _teardown function was defined, + // enforce for track event removals + if ( trackEvent._natives._teardown ) { + trackEvent._natives._teardown.call( this, trackEvent ); + } + + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + if ( isfn ) { + pluginFn.call( this, definition.call( this, trackEvent ), trackEvent ); + } else { + + // Supports user defined track event id + trackEvent._id = trackEvent.id || trackEvent._id || Popcorn.guid( trackEvent._natives.type ); + + if ( trackEvent._natives && trackEvent._natives._setup ) { + + trackEvent._natives._setup.call( this, trackEvent ); + + this.emit( "tracksetup", Popcorn.extend( {}, trackEvent, { + plugin: trackEvent._natives.type, + type: "tracksetup", + track: trackEvent + })); + } + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, trackEvent ); + } + + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: trackEvent, + track: trackEvent + }); + + return this; + } + + if ( trackEvent._natives.type !== "cue" ) { + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: newOpts, + track: trackEvent + }); + } + + return this; + } + } + + this.data.running[ name ] = this.data.running[ name ] || []; + + // Merge with defaults if they exist, make sure per call is prioritized + defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {}; + mergedSetupOpts = Popcorn.extend( {}, defaults, options ); + + pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition, + mergedSetupOpts ); + + return this; + }; + + // 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; + + // 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 = { + ajax: null, + url: "", + data: "", + dataType: "", + success: Popcorn.nop, + type: "GET", + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8" + }; + + Popcorn.xhr = function( options ) { + var settings; + + 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; + } + + // Merge the "setup" defaults and custom "options" + // into a new plain object. + settings = Popcorn.extend( {}, setup, options ); + + // Create new XMLHttpRequest object + settings.ajax = new XMLHttpRequest(); + + 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; + } + + // Open the request + settings.ajax.open( settings.type, settings.url, settings.async ); + + // For POST, set the content-type request header + if ( settings.type === "POST" ) { + settings.ajax.setRequestHeader( + "Content-Type", settings.contentType + ); + } + + 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" ), + isFired = false, + params = [], + rjsonp = /(=)\?(?=&|$)|\?\?/, + replaceInUrl, prefix, paramStr, callback, callparam; + + if ( !isScript ) { + + // is there a calback already in the url + callparam = url.match( /(callback=[^&]*)/ ); + + if ( callparam !== null && callparam.length ) { + + prefix = callparam[ 1 ].split( "=" )[ 1 ]; + + // Since we need to support developer specified callbacks + // and placeholders in harmony, make sure matches to "callback=" + // aren't just placeholders. + // We coded ourselves into a corner here. + // JSONP callbacks should never have been + // allowed to have developer specified callbacks + if ( prefix === "?" ) { + prefix = "jsonp"; + } + + // get the callback name + callback = Popcorn.guid( prefix ); + + // replace existing callback name with unique callback name + url = url.replace( /(callback=[^&]*)/, "callback=" + callback ); + } else { + + callback = Popcorn.guid( "jsonp" ); + + if ( rjsonp.test( url ) ) { + url = url.replace( rjsonp, "$1" + callback ); + } + + // split on first question mark, + // this is to capture the query string + params = url.split( /\?(.+)?/ ); + + // rebuild url with callback + url = params[ 0 ] + "?"; + if ( params[ 1 ] ) { + url += params[ 1 ] + "&"; + } + url += "callback=" + callback; + } + + // Define the JSONP success callback globally + window[ callback ] = function( data ) { + // Fire success callbacks + success && success( data ); + isFired = true; + }; + } + + 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.addEventListener( "error", function( e ) { + // Handling remote script loading callbacks + success && success( { error: e } ); + + // Executing for JSONP requests + if ( !isScript ) { + // 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); +(function( global, Popcorn ) { + + var navigator = global.navigator; + + // Initialize locale data + // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes + function initLocale( arg ) { + + var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ), + parts = locale.split( "-" ); + + // Setup locale data table + return { + iso6391: locale, + language: parts[ 0 ] || "", + region: parts[ 1 ] || "" + }; + } + + // Declare locale data table + var localeData = initLocale( navigator.userLanguage || navigator.language ); + + Popcorn.locale = { + + // Popcorn.locale.get() + // returns reference to privately + // defined localeData + get: function() { + return localeData; + }, + + // Popcorn.locale.set( string|object ); + set: function( arg ) { + + localeData = initLocale( arg ); + + Popcorn.locale.broadcast(); + + return localeData; + }, + + // Popcorn.locale.broadcast( type ) + // Sends events to all popcorn media instances that are + // listening for locale events + broadcast: function( type ) { + + var instances = Popcorn.instances, + length = instances.length, + idx = 0, + instance; + + type = type || "locale:changed"; + + // Iterate all current instances + for ( ; idx < length; idx++ ) { + instance = instances[ idx ]; + + // For those instances with locale event listeners, + // trigger a locale change event + if ( type in instance.data.events ) { + instance.trigger( type ); + } + } + } + }; +})( this, this.Popcorn );(function( Popcorn ) { + + var + + AP = Array.prototype, + OP = Object.prototype, + + forEach = AP.forEach, + slice = AP.slice, + hasOwn = OP.hasOwnProperty, + toString = OP.toString; + + // stores parsers keyed on filetype + Popcorn.parsers = {}; + + // An interface for extending Popcorn + // with parser functionality + Popcorn.parser = function( name, type, definition ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // fixes parameters for overloaded function call + if ( typeof type === "function" && !definition ) { + definition = type; + type = ""; + } + + if ( typeof definition !== "function" || typeof type !== "string" ) { + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + + var natives = Popcorn.events.all, + parseFn, + parser = {}; + + parseFn = function( filename, callback, options ) { + + if ( !filename ) { + return this; + } + + // fixes parameters for overloaded function call + if ( typeof callback !== "function" && !options ) { + options = callback; + callback = null; + } + + var that = this; + + Popcorn.xhr({ + url: filename, + dataType: type, + success: function( data ) { + + var tracksObject = definition( data, options ), + tracksData, + tracksDataLen, + tracksDef, + idx = 0; + + tracksData = tracksObject.data || []; + tracksDataLen = tracksData.length; + tracksDef = null; + + // If no tracks to process, return immediately + if ( !tracksDataLen ) { + return; + } + + // Create tracks out of parsed object + for ( ; idx < tracksDataLen; idx++ ) { + + tracksDef = tracksData[ idx ]; + + for ( var key in tracksDef ) { + + if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) { + + that[ key ]( tracksDef[ key ] ); + } + } + } + if ( callback ) { + callback(); + } + } + }); + + return this; + }; + + // Assign new named definition + parser[ name ] = parseFn; + + // Extend Popcorn.p with new named definition + Popcorn.extend( Popcorn.p, parser ); + + // keys the function name by filetype extension + //Popcorn.parsers[ name ] = true; + + return parser; + }; +})( Popcorn ); +(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\-\_\.]+))$/; + + 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 { + + // Asynchronous so that users can catch this event + setTimeout( function() { + basePlayer.dispatchEvent( "error" ); + }, 0 ); + } + + 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 ](); + } + }; + }; + + // Popcorn.smart will attempt to find you a wrapper or player. If it can't do that, + // it will default to using an HTML5 video in the target. + Popcorn.smart = function( target, src, options ) { + var node = typeof target === "string" ? Popcorn.dom.find( target ) : target, + i, srci, j, media, mediaWrapper, popcorn, srcLength, + // We leave HTMLVideoElement and HTMLAudioElement wrappers out + // of the mix, since we'll default to HTML5 video if nothing + // else works. Waiting on #1254 before we add YouTube to this. + wrappers = "HTMLYouTubeVideoElement HTMLVimeoVideoElement HTMLSoundCloudAudioElement HTMLNullVideoElement".split(" "); + + if ( !node ) { + Popcorn.error( "Specified target `" + target + "` was not found." ); + return; + } + + // If our src is not an array, create an array of one. + src = typeof src === "string" ? [ src ] : src; + + // Loop through each src, and find the first playable. + for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { + srci = src[ i ]; + + // See if we can use a wrapper directly, if not, try players. + for ( j = 0; j < wrappers.length; j++ ) { + mediaWrapper = Popcorn[ wrappers[ j ] ]; + if ( mediaWrapper && mediaWrapper._canPlaySrc( srci ) === "probably" ) { + media = mediaWrapper( node ); + popcorn = Popcorn( media, options ); + // Set src, but not until after we return the media so the caller + // can get error events, if any. + setTimeout( function() { + media.src = srci; + }, 0 ); + return popcorn; + } + } + + // No wrapper can play this, check players. + for ( var key in Popcorn.player.registry ) { + if ( Popcorn.player.registry.hasOwnProperty( key ) ) { + if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, srci ) ) { + // Popcorn.smart( player, src, /* options */ ) + return Popcorn[ key ]( node, srci, options ); + } + } + } + } + + // If we don't have any players or wrappers that can handle this, + // Default to using HTML5 video. Similar to the HTMLVideoElement + // wrapper, we put a video in the div passed to us via: + // Popcorn.smart( div, src, options ) + var videoHTML, + videoElement, + videoID = Popcorn.guid( "popcorn-video-" ), + videoHTMLContainer = document.createElement( "div" ); + + videoHTMLContainer.style.width = "100%"; + videoHTMLContainer.style.height = "100%"; + + // If we only have one source, do not bother with source elements. + // This means we don't have the IE9 hack, + // and we can properly listen to error events. + // That way an error event can be told to backup to Flash if it fails. + if ( src.length === 1 ) { + videoElement = document.createElement( "video" ); + videoElement.id = videoID; + node.appendChild( videoElement ); + setTimeout( function() { + // Hack to decode html characters like & to & + var decodeDiv = document.createElement( "div" ); + decodeDiv.innerHTML = src[ 0 ]; + + videoElement.src = decodeDiv.firstChild.nodeValue; + }, 0 ); + return Popcorn( '#' + videoID, options ); + } + + node.appendChild( videoHTMLContainer ); + // IE9 doesn't like dynamic creation of source elements on ";e.innerHTML=v;b&&b.events&&b.events.error&&f.addEventListener("error",b.events.error,false);return m("#"+w,b)}else m.error("Specified target `"+k+"` was not found.")}})(Popcorn);(function(m){document.addEventListener("DOMContentLoaded",function(){var i=document.querySelectorAll("[data-timeline-sources]");m.forEach(i,function(k,d){var b=i[d],f,h,e;if(!b.id)b.id=m.guid("__popcorn");if(b.nodeType&&b.nodeType===1){e=m("#"+b.id);f=(b.getAttribute("data-timeline-sources")||"").split(",");f[0]&&m.forEach(f,function(n){h=n.split("!");if(h.length===1){h=n.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/)[2].split(".");h[0]="parse"+h[1].toUpperCase();h[1]=n}f[0]&&e[h[0]]&&e[h[0]](h[1])});e.autoplay()&& +e.play()}})},false)})(Popcorn);(function(m,i){function k(b){var f=k.options;b=f.parser[f.strictMode?"strict":"loose"].exec(b);for(var h={},e=14;e--;)h[f.key[e]]=b[e]||"";h[f.q.name]={};h[f.key[12]].replace(f.q.parser,function(n,u,q){if(u)h[f.q.name][u]=q});return h}k.options={strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, +loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};var d={length:0,start:m.nop,end:m.nop};window.MediaError=window.MediaError||function(){function b(f,h){this.code=f||null;this.message=h||""}b.MEDIA_ERR_NONE_ACTIVE=0;b.MEDIA_ERR_ABORTED=1;b.MEDIA_ERR_NETWORK=2;b.MEDIA_ERR_DECODE=3;b.MEDIA_ERR_NONE_SUPPORTED=4;return b}();m._MediaElementProto=function(){var b= +{},f;Object.prototype.__defineGetter__||(b=i.createElement("div"));b._util={type:"HTML5",TIMEUPDATE_MS:250,MIN_WIDTH:300,MIN_HEIGHT:150,isAttributeSet:function(h){return typeof h==="string"||h===true},parseUri:k};b.addEventListener=function(h,e,n){i.addEventListener(this._eventNamespace+h,e,n)};b.removeEventListener=function(h,e,n){i.removeEventListener(this._eventNamespace+h,e,n)};b.dispatchEvent=function(h){var e=i.createEvent("CustomEvent");e.initCustomEvent(this._eventNamespace+h,false,false, +{type:h,target:this.parentNode,data:null});i.dispatchEvent(e)};b.load=m.nop;b.canPlayType=function(){return""};b.getBoundingClientRect=function(){return f.getBoundingClientRect()};b.NETWORK_EMPTY=0;b.NETWORK_IDLE=1;b.NETWORK_LOADING=2;b.NETWORK_NO_SOURCE=3;b.HAVE_NOTHING=0;b.HAVE_METADATA=1;b.HAVE_CURRENT_DATA=2;b.HAVE_FUTURE_DATA=3;b.HAVE_ENOUGH_DATA=4;Object.defineProperties(b,{currentSrc:{get:function(){return this.src!==undefined?this.src:""},configurable:true},parentNode:{get:function(){return f}, +set:function(h){f=h},configurable:true},preload:{get:function(){return"auto"},set:m.nop,configurable:true},controls:{get:function(){return true},set:m.nop,configurable:true},poster:{get:function(){return""},set:m.nop,configurable:true},crossorigin:{get:function(){return""},configurable:true},played:{get:function(){return d},configurable:true},seekable:{get:function(){return d},configurable:true},buffered:{get:function(){return d},configurable:true},defaultMuted:{get:function(){return false},configurable:true}, +defaultPlaybackRate:{get:function(){return 1},configurable:true},style:{get:function(){return this.parentNode.style},configurable:true},id:{get:function(){return this.parentNode.id},configurable:true}});return b}})(Popcorn,window.document);(function(m,i){function k(){return"maybe"}function d(b,f){var h=typeof b==="string"?i.querySelector(b):b,e=i.createElement(f);h.appendChild(e);e._canPlaySrc=k;return e}m.HTMLVideoElement=function(b){return d(b,"video")};m.HTMLVideoElement._canPlaySrc=k;m.HTMLAudioElement=function(b){return d(b,"audio")};m.HTMLAudioElement._canPlaySrc=k})(Popcorn,window.document);(function(m,i,k){function d(){if(i.jwplayer){n=true;for(var v=q.length;v--;){q[v]();delete q[v]}}else setTimeout(d,100)}function b(){if(!u){if(!i.jwplayer){var v=k.createElement("script");v.src="https://jwpsrv.com/library/zaIF4JI9EeK2FSIACpYGxA.js";var w=k.getElementsByTagName("script")[0];w.parentNode.insertBefore(v,w)}u=true;d()}return n}function f(v){q.unshift(v)}function h(v){function w(L){C.unshift(L)}function o(){var L=K.getDuration();if(L==-1||L==undefined)setTimeout(o,0);else{r.duration=L; +y.dispatchEvent("durationchange");M=true;r.readyState=y.HAVE_METADATA;y.dispatchEvent("loadedmetadata");y.dispatchEvent("loadeddata");r.readyState=y.HAVE_FUTURE_DATA;y.dispatchEvent("canplay");for(B=true;C.length;){C[0]();C.shift()}r.readyState=y.HAVE_ENOUGH_DATA;y.dispatchEvent("canplaythrough")}}function z(){if(x)x=false;else if(H){H=false;o()}else l()}function J(){if(r.seeking){r.ended=false;r.seeking=false;y.dispatchEvent("timeupdate");y.dispatchEvent("seeked");y.dispatchEvent("canplay");y.dispatchEvent("canplaythrough")}} +function E(){K.onPause(z);K.onTime(function(){if(!r.ended&&!r.seeking){r.currentTime=K.getPosition();y.dispatchEvent("timeupdate")}});K.onSeek(J);K.onPlay(function(){if(!r.ended)if(S){S=false;if(r.autoplay||!r.paused){r.paused=false;w(a);o()}else{p=H=true;K.pause(true)}}else if(p){p=false;x=true;K.pause(true)}else a()});K.onBufferChange(c);K.onComplete(j);K.play(true)}function g(L){var T={name:"MediaError"};T.message=L.message;T.code=L.code||5;r.error=T;y.dispatchEvent("error")}function G(L){if(y._canPlaySrc(L)){var T= +y._util.parseUri(L).queryKey;r.controls=T.controls=T.controls||r.controls;r.src=L;if(b()){if(M)M&&K&&K.destroy();T={width:"100%",height:"100%",autostart:r.autoplay,controls:r.controls};if(typeof L=="string")T.file=L;else T.sources=L;jwplayer(D.id).setup(T);K=jwplayer(D.id);K.onReady(E);K.onError(g);jwplayer.utils.log=function(F,V){if(typeof console!=="undefined"&&typeof console.log!=="undefined")V?console.log(F,V):console.log(F);F==="No suitable players found and fallback enabled"&&g({message:F,code:4})}; +r.networkState=y.NETWORK_LOADING;y.dispatchEvent("loadstart");y.dispatchEvent("progress")}else f(function(){G(L)})}else{r.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};y.dispatchEvent("error")}}function A(L){r.currentTime=L;if(B){O();K.seek(L)}else w(function(){O();K.seek(L)})}function O(){r.seeking=true;if(r.paused)p=true;y.dispatchEvent("seeking")}function a(){r.paused=false;if(M&&N){N=false;if(r.loop&&!R||!r.loop){R=true;y.dispatchEvent("play")}y.dispatchEvent("playing")}} +function c(){y.dispatchEvent("progress")}function l(){r.paused=true;if(!N){N=true;y.dispatchEvent("pause")}}function j(){if(r.loop)A(0);else{r.ended=true;l();y.dispatchEvent("timeupdate");y.dispatchEvent("ended")}}function s(L){r.volume=L;if(B){K.setVolume(r.volume*100);y.dispatchEvent("volumechange")}else w(function(){s(r.volume)})}function t(L){r.muted=L;if(B){K.setMute(L);y.dispatchEvent("volumechange")}else w(function(){t(r.muted)})}if(!i.postMessage)throw"ERROR: HTMLJWPlayerVideoElement requires window.postMessage"; +var y=new m._MediaElementProto,D=typeof v==="string"?k.querySelector(v):v,r={src:e,networkState:y.NETWORK_EMPTY,readyState:y.HAVE_NOTHING,seeking:false,autoplay:e,preload:e,controls:false,loop:false,poster:e,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},M=false,x=false,p=false,B=false,R=false,K,N=true,C=[],S=true,H=false;y._eventNamespace=m.guid("HTMLJWPlayerVideoElement::");y.parentNode=D;y._util.type="JWPlayer";y.play=function(){y.dispatchEvent("play");r.paused= +false;if(B){if(r.ended){A(0);r.ended=false}K.play(true)}else w(function(){y.play()})};y.pause=function(){r.paused=true;B?K.pause(true):w(function(){y.pause()})};Object.defineProperties(y,{src:{get:function(){return r.src},set:function(L){L&&L!==r.src&&G(L)}},autoplay:{get:function(){return r.autoplay},set:function(L){r.autoplay=y._util.isAttributeSet(L)}},loop:{get:function(){return r.loop},set:function(L){r.loop=y._util.isAttributeSet(L)}},width:{get:function(){return y.parentNode.offsetWidth}}, +height:{get:function(){return y.parentNode.offsetHeight}},currentTime:{get:function(){return r.currentTime},set:function(L){A(L)}},duration:{get:function(){return K.getDuration()}},ended:{get:function(){return r.ended}},paused:{get:function(){return r.paused}},seeking:{get:function(){return r.seeking}},readyState:{get:function(){return r.readyState}},networkState:{get:function(){return r.networkState}},volume:{get:function(){return r.volume},set:function(L){if(L<0||L>1)throw"Volume value must be between 0.0 and 1.0"; +s(L)}},muted:{get:function(){return r.muted},set:function(L){t(y._util.isAttributeSet(L))}},error:{get:function(){return r.error}},buffered:{get:function(){return{start:function(L){if(L===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(L){if(L===0){L=K.getDuration();if(!L)return 0;return L*(K.getBuffer()/100)}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}}}});y._canPlaySrc=m.HTMLJWPlayerVideoElement._canPlaySrc;y.canPlayType=m.HTMLJWPlayerVideoElement.canPlayType;return y}var e= +"",n=false,u=false,q=[];m.HTMLJWPlayerVideoElement=function(v){return new h(v)};m.HTMLJWPlayerVideoElement._canPlaySrc=function(v){if(typeof v=="string"){if(/.+\.+/g.exec(v))return"probably"}else return"probably"};m.HTMLJWPlayerVideoElement.canPlayType=function(){return"probably"}})(Popcorn,window,document);(function(m,i){function k(h){this.startTime=0;this.currentTime=h.currentTime||0;this.duration=h.duration||NaN;this.playInterval=null;this.paused=true;this.playbackRate=this.defaultPlaybackRate=1;this.ended=h.endedCallback||m.nop}function d(h){function e(a){A.push(a)}function n(){if(!E)return 0;return g.currentTime}function u(a){if(a!==n())if(E){G.seeking=true;o.dispatchEvent("seeking");g.seekTo(a);G.ended=false;G.seeking=false;o.dispatchEvent("timeupdate");o.dispatchEvent("seeked");o.dispatchEvent("canplay"); +o.dispatchEvent("canplaythrough")}else e(function(){u(a)})}function q(){o.dispatchEvent("timeupdate")}function v(){G.paused=true;clearInterval(O);o.dispatchEvent("pause")}function w(){if(G.loop){u(0);o.play()}else{G.ended=true;v();o.dispatchEvent("timeupdate");o.dispatchEvent("ended")}}var o=new m._MediaElementProto,z=typeof h==="string"?i.querySelector(h):h,J=i.createElement("div"),E=false,g,G={src:b,networkState:o.NETWORK_EMPTY,readyState:o.HAVE_NOTHING,autoplay:b,preload:b,controls:b,loop:false, +poster:b,volume:1,muted:false,width:z.width|0?z.width:o._util.MIN_WIDTH,height:z.height|0?z.height:o._util.MIN_HEIGHT,seeking:false,ended:false,paused:1,error:null},A=[],O;o._eventNamespace=m.guid("HTMLNullVideoElement::");o.parentNode=z;o._util.type="NullVideo";o.play=function(){if(E){g.play();if(G.paused){if(G.paused===1){G.paused=false;o.dispatchEvent("play");o.dispatchEvent("playing")}else{if(G.ended){u(0);G.ended=false}if(G.paused){G.paused=false;G.loop||o.dispatchEvent("play");o.dispatchEvent("playing")}}O= +setInterval(q,o._util.TIMEUPDATE_MS)}}else e(function(){o.play()})};o.pause=function(){if(E){g.pause();G.paused||v()}else e(function(){o.pause()})};Object.defineProperties(o,{src:{get:function(){return G.src},set:function(a){if(a&&a!==G.src)if(o._canPlaySrc(a)){G.src=a;if(E)if(E&&g){g.pause();g=null;z.removeChild(J);J=i.createElement("div")}J.width=G.width;J.height=G.height;z.appendChild(J);a=f.exec(a);g=new k({currentTime:+a[1],duration:+a[2],endedCallback:w});o.dispatchEvent("loadstart");o.dispatchEvent("progress"); +o.dispatchEvent("durationchange");E=true;G.networkState=o.NETWORK_IDLE;G.readyState=o.HAVE_METADATA;o.dispatchEvent("loadedmetadata");o.dispatchEvent("loadeddata");G.readyState=o.HAVE_FUTURE_DATA;o.dispatchEvent("canplay");G.readyState=o.HAVE_ENOUGH_DATA;for(o.dispatchEvent("canplaythrough");A.length;){a=A.shift();a()}G.autoplay&&o.play()}else{G.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};o.dispatchEvent("error")}}},autoplay:{get:function(){return G.autoplay}, +set:function(a){G.autoplay=o._util.isAttributeSet(a)}},loop:{get:function(){return G.loop},set:function(a){G.loop=o._util.isAttributeSet(a)}},width:{get:function(){return J.width},set:function(a){J.width=a;G.width=J.width}},height:{get:function(){return J.height},set:function(a){J.height=a;G.height=J.height}},currentTime:{get:function(){return n()},set:function(a){u(a)}},duration:{get:function(){return g?g.duration:NaN}},ended:{get:function(){return G.ended}},paused:{get:function(){return G.paused}}, +seeking:{get:function(){return G.seeking}},readyState:{get:function(){return G.readyState}},networkState:{get:function(){return G.networkState}},volume:{get:function(){return G.volume},set:function(a){if(a<0||a>1)throw"Volume value must be between 0.0 and 1.0";G.volume=a;o.dispatchEvent("volumechange")}},muted:{get:function(){return G.muted},set:function(a){a=o._util.isAttributeSet(a);G.muted=a;o.dispatchEvent("volumechange")}},playbackRate:{get:function(){return g.playbackRate},set:function(a){g.playbackRate= +a;o.dispatchEvent("ratechange")}},error:{get:function(){return G.error}}});o._canPlaySrc=m.HTMLNullVideoElement._canPlaySrc;o.canPlayType=m.HTMLNullVideoElement.canPlayType;return o}var b="",f=/#t=(\d+\.?\d*)?,?(\d+\.?\d*)/;k.prototype={play:function(){var h=this;if(this.paused){this.paused=false;this.startTime=Date.now();this.playInterval=setInterval(function(){h.currentTime+=(Date.now()-h.startTime)/(1E3/h.playbackRate);h.startTime=Date.now();if(h.currentTime>=h.duration){h.pause(h.duration);h.ended()}h.currentTime< +0&&h.pause(0)},16)}},pause:function(){if(!this.paused){this.paused=true;clearInterval(this.playInterval)}},seekTo:function(h){h=h<0?0:h;this.currentTime=h=h>this.duration?this.duration:h}};m.HTMLNullVideoElement=function(h){return new d(h)};m.HTMLNullVideoElement._canPlaySrc=function(h){return f.test(h)?"probably":b};m.HTMLNullVideoElement.canPlayType=function(h){return h==="video/x-nullvideo"?"probably":b}})(Popcorn,document);(function(m,i,k){function d(){if(!u){m.getScript("https://w.soundcloud.com/player/api.js",function(){m.getScript("https://connect.soundcloud.com/sdk.js",function(){n=true;SC.initialize({client_id:"PRaNFlda6Bhf5utPjUsptg"});for(var v=q.length;v--;){q[v]();delete q[v]}})});u=true}return n}function b(v){q.unshift(v)}function f(v){function w(C){B.unshift(C)}function o(){p.bind(SC.Widget.Events.LOAD_PROGRESS,function(C){O({type:"loadProgress",data:C.currentPosition/1E3})});p.bind(SC.Widget.Events.PLAY_PROGRESS, +function(C){O({type:"playProgress",data:C.currentPosition/1E3})});p.bind(SC.Widget.Events.PLAY,function(){O({type:"play"})});p.bind(SC.Widget.Events.PAUSE,function(){O({type:"pause"})});p.bind(SC.Widget.Events.SEEK,function(){p.getPosition(function(C){C=C/1E3;if(r.seeking)if(Math.floor(C)!==Math.floor(r.currentTime))p.seekTo(r.currentTime*1E3);else{r.ended=false;r.seeking=false;t.dispatchEvent("timeupdate");t.dispatchEvent("seeked");t.dispatchEvent("canplay");t.dispatchEvent("canplaythrough")}else O({type:"seek", +data:C})})});p.bind(SC.Widget.Events.FINISH,function(){O({type:"finish"})});M=true;p.getDuration(J)}function z(){p.bind(SC.Widget.Events.PLAY_PROGRESS,function(C){p.setVolume(0);if(C.currentPosition>0){p.unbind(SC.Widget.Events.PLAY_PROGRESS);p.bind(SC.Widget.Events.PAUSE,function(){p.unbind(SC.Widget.Events.PAUSE);p.setVolume(1);p.bind(SC.Widget.Events.SEEK,function(){p.unbind(SC.Widget.Events.SEEK);o()});p.seekTo(0)});p.pause()}});p.play()}function J(C){C/=1E3;var S=r.duration;if(S!==C){r.duration= +C;t.dispatchEvent("durationchange");if(isNaN(S)){r.networkState=t.NETWORK_IDLE;r.readyState=t.HAVE_METADATA;t.dispatchEvent("loadedmetadata");t.dispatchEvent("loadeddata");r.readyState=t.HAVE_FUTURE_DATA;t.dispatchEvent("canplay");r.readyState=t.HAVE_ENOUGH_DATA;t.dispatchEvent("canplaythrough");for(C=B.length;C--;){B[C]();delete B[C]}r.paused&&r.autoplay&&t.play()}}}function E(C){function S(){r.seeking=true;t.dispatchEvent("seeking");p.seekTo(C)}r.currentTime=C;C*=1E3;M?S():addMediaReadyCallback(S)} +function g(){r.paused=true;if(!x){x=true;clearInterval(R);t.dispatchEvent("pause")}}function G(){t.dispatchEvent("timeupdate")}function A(C){r.currentTime=C;C!==N&&t.dispatchEvent("timeupdate");N=C}function O(C){switch(C.type){case "loadProgress":t.dispatchEvent("progress");break;case "playProgress":A(C.data);break;case "play":if(!K){K=setInterval(a,h);r.loop&&t.dispatchEvent("play")}R=setInterval(G,t._util.TIMEUPDATE_MS);r.paused=false;if(x){x=false;r.loop||t.dispatchEvent("play");t.dispatchEvent("playing")}break; +case "pause":g();break;case "finish":if(r.loop){E(0);t.play()}else{r.ended=true;t.pause();g();t.dispatchEvent("timeupdate");t.dispatchEvent("ended")}break;case "seek":A(C.data)}}function a(){r.ended||p.getPosition(function(C){A(C/1E3)})}function c(C){if(t._canPlaySrc(C)){r.src=C;if(M)if(M&&p){clearInterval(K);p.pause();p.unbind(SC.Widget.Events.READY);p.unbind(SC.Widget.Events.LOAD_PROGRESS);p.unbind(SC.Widget.Events.PLAY_PROGRESS);p.unbind(SC.Widget.Events.PLAY);p.unbind(SC.Widget.Events.PAUSE); +p.unbind(SC.Widget.Events.SEEK);p.unbind(SC.Widget.Events.FINISH);y.removeChild(D);D=k.createElement("iframe")}if(d()){M=false;SC.get("/resolve",{url:C},function(S){var H;if(S.errors){H={name:"MediaError"};if(S.errors[0])if(S.errors[0].error_message==="404 - Not Found"){H.message="Video not found.";H.code=MediaError.MEDIA_ERR_NETWORK}r.error=H;t.dispatchEvent("error")}D.id=m.guid("soundcloud-");D.width=r.width;D.height=r.height;D.frameBorder=0;D.webkitAllowFullScreen=true;D.mozAllowFullScreen=true; +D.allowFullScreen=true;s(r.controls);y.appendChild(D);D.onload=function(){D.onload=null;p=SC.Widget(D);p.bind(SC.Widget.Events.READY,z);r.networkState=t.NETWORK_LOADING;t.dispatchEvent("loadstart");t.dispatchEvent("progress")};D.src="https://w.soundcloud.com/player/?url="+S.uri+"&show_artwork=false&buying=false&liking=false&sharing=false&download=false&show_comments=false&show_user=false&single_active=false"})}else b(function(){c(C)})}else{r.error={name:"MediaError",message:"Media Source Not Supported", +code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};t.dispatchEvent("error")}}function l(C){r.volume=C;if(M){p.setVolume(C);t.dispatchEvent("volumechange")}else w(function(){l(C)})}function j(C){if(M)if(C){r.muted=r.volume;l(0)}else{r.muted=0;l(r.muted)}else{r.muted=C?1:0;w(function(){j(C)})}}function s(C){if(M){D.style.position="absolute";D.style.visibility=C?"visible":"hidden"}else{D.style.opacity=C?"1":"0";D.style.pointerEvents=C?"auto":"none"}r.controls=C}if(!i.postMessage)throw"ERROR: HTMLSoundCloudAudioElement requires window.postMessage"; +var t=new m._MediaElementProto,y=typeof v==="string"?m.dom.find(v):v,D=k.createElement("iframe"),r={src:e,networkState:t.NETWORK_EMPTY,readyState:t.HAVE_NOTHING,seeking:false,autoplay:e,preload:e,controls:false,loop:false,poster:e,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,width:y.width|0?y.width:t._util.MIN_WIDTH,height:y.height|0?y.height:t._util.MIN_HEIGHT,error:null},M=false,x=true,p,B=[],R,K,N=0;t._eventNamespace=m.guid("HTMLSoundCloudAudioElement::");t.parentNode=y; +t._util.type="SoundCloud";t.play=function(){r.paused=false;if(M){r.ended&&E(0);p.play()}else w(function(){t.play()})};t.pause=function(){r.paused=true;M?p.pause():w(function(){t.pause()})};Object.defineProperties(t,{src:{get:function(){return r.src},set:function(C){C&&C!==r.src&&c(C)}},autoplay:{get:function(){return r.autoplay},set:function(C){r.autoplay=t._util.isAttributeSet(C)}},loop:{get:function(){return r.loop},set:function(C){r.loop=t._util.isAttributeSet(C)}},width:{get:function(){return D.width}, +set:function(C){D.width=C;r.width=D.width}},height:{get:function(){return D.height},set:function(C){D.height=C;r.height=D.height}},currentTime:{get:function(){return r.currentTime},set:function(C){E(C)}},duration:{get:function(){return r.duration}},ended:{get:function(){return r.ended}},paused:{get:function(){return r.paused}},seeking:{get:function(){return r.seeking}},readyState:{get:function(){return r.readyState}},networkState:{get:function(){return r.networkState}},volume:{get:function(){return r.muted> +0?r.muted:r.volume},set:function(C){if(C<0||C>1)throw"Volume value must be between 0.0 and 1.0";l(C)}},muted:{get:function(){return r.muted>0},set:function(C){j(t._util.isAttributeSet(C))}},error:{get:function(){return r.error}},controls:{get:function(){return r.controls},set:function(C){s(!!C)}}});t._canPlaySrc=m.HTMLSoundCloudAudioElement._canPlaySrc;t.canPlayType=m.HTMLSoundCloudAudioElement.canPlayType;return t}var h=16,e="",n=false,u=false,q=[];m.HTMLSoundCloudAudioElement=function(v){return new f(v)}; +m.HTMLSoundCloudAudioElement._canPlaySrc=function(v){return/(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/.test(v)?"probably":e};m.HTMLSoundCloudAudioElement.canPlayType=function(v){return v==="audio/x-soundcloud"?"probably":e}})(Popcorn,window,document);(function(m,i,k){function d(n){var u=this,q=n.src.split("?")[0];if(q.substr(0,2)==="//")q=i.location.protocol+q;"play pause paused seekTo unload getCurrentTime getDuration getVideoEmbedCode getVideoHeight getVideoWidth getVideoUrl getColor setColor setLoop getVolume setVolume addEventListener".split(" ").forEach(function(v){u[v]=function(w){w=JSON.stringify({method:v,value:w});n.contentWindow&&n.contentWindow.postMessage(w,q)}})}function b(n){function u(x){y.unshift(x)}function q(x){var p=c.duration; +if(p!==x){c.duration=x;A.dispatchEvent("durationchange");if(isNaN(p)){c.networkState=A.NETWORK_IDLE;c.readyState=A.HAVE_METADATA;A.dispatchEvent("loadedmetadata");A.dispatchEvent("loadeddata");c.readyState=A.HAVE_FUTURE_DATA;A.dispatchEvent("canplay");c.readyState=A.HAVE_ENOUGH_DATA;A.dispatchEvent("canplaythrough");c.autoplay&&A.play();for(x=y.length;x--;){y[x]();delete y[x]}}}}function v(x){if(l){c.seeking=true;A.dispatchEvent("seeking");s.seekTo(x)}else u(function(){v(x)})}function w(){A.dispatchEvent("timeupdate")} +function o(x){(c.currentTime=x)!==M&&A.dispatchEvent("timeupdate");M=c.currentTime}function z(x){if(x.origin===e){var p;try{p=JSON.parse(x.data)}catch(B){console.warn(B)}if(p.player_id==j)switch(p.event){case "ready":s=new d(a);s.addEventListener("loadProgress");s.addEventListener("pause");s.setVolume(0);s.play();break;case "loadProgress":if(parseFloat(p.data.duration)>0&&!l){l=true;s.pause()}break;case "pause":s.setVolume(1);i.removeEventListener("message",z,false);i.addEventListener("message",J, +false);s.addEventListener("loadProgress");s.addEventListener("playProgress");s.addEventListener("play");s.addEventListener("pause");s.addEventListener("finish");s.addEventListener("seek");s.getDuration();c.networkState=A.NETWORK_LOADING;A.dispatchEvent("loadstart");A.dispatchEvent("progress")}}}function J(x){if(x.origin===e){var p;try{p=JSON.parse(x.data)}catch(B){console.warn(B)}if(p.player_id==j){switch(p.method){case "getCurrentTime":o(parseFloat(p.value));break;case "getDuration":q(parseFloat(p.value)); +break;case "getVolume":x=parseFloat(p.value);if(c.volume!==x){c.volume=x;A.dispatchEvent("volumechange")}}switch(p.event){case "loadProgress":A.dispatchEvent("progress");q(parseFloat(p.data.duration));break;case "playProgress":o(parseFloat(p.data.seconds));break;case "play":c.ended&&v(0);if(!r){r=setInterval(E,f);c.loop&&A.dispatchEvent("play")}D=setInterval(w,A._util.TIMEUPDATE_MS);c.paused=false;if(t){t=false;c.loop||A.dispatchEvent("play");A.dispatchEvent("playing")}break;case "pause":c.paused= +true;if(!t){t=true;clearInterval(D);A.dispatchEvent("pause")}break;case "finish":if(c.loop){v(0);A.play()}else{c.ended=true;A.dispatchEvent("ended")}break;case "seek":o(parseFloat(p.data.seconds));c.seeking=false;A.dispatchEvent("timeupdate");A.dispatchEvent("seeked");A.dispatchEvent("canplay");A.dispatchEvent("canplaythrough")}}}}function E(){s.getCurrentTime()}function g(x){c.volume=x;if(l){s.setVolume(x);A.dispatchEvent("volumechange")}else u(function(){g(x)})}function G(x){if(l)if(x){c.muted= +c.volume;g(0)}else{c.muted=0;g(c.muted)}else{c.muted=x?1:0;u(function(){G(x)})}}if(!i.postMessage)throw"ERROR: HTMLVimeoVideoElement requires window.postMessage";var A=new m._MediaElementProto,O=typeof n==="string"?m.dom.find(n):n,a=k.createElement("iframe"),c={src:h,networkState:A.NETWORK_EMPTY,readyState:A.HAVE_NOTHING,seeking:false,autoplay:h,preload:h,controls:false,loop:false,poster:h,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,error:null},l=false,j=m.guid(),s,t=true, +y=[],D,r,M=0;A._eventNamespace=m.guid("HTMLVimeoVideoElement::");A.parentNode=O;A._util.type="Vimeo";A.play=function(){c.paused=false;l?s.play():u(function(){A.play()})};A.pause=function(){c.paused=true;l?s.pause():u(function(){A.pause()})};Object.defineProperties(A,{src:{get:function(){return c.src},set:function(x){if(x&&x!==c.src)if(A._canPlaySrc(x)){c.src=x;if(l)if(l&&s){clearInterval(r);s.pause();i.removeEventListener("message",J,false);O.removeChild(a);a=k.createElement("iframe")}l=false;x=A._util.parseUri(x); +var p=x.queryKey,B,R=["api=1","player_id="+j,"title=0","byline=0","portrait=0"];c.loop=p.loop==="1"||c.loop;delete p.loop;c.autoplay=p.autoplay==="1"||c.autoplay;delete p.autoplay;x=e+"/video/"+/\d+$/.exec(x.path)+"?";for(B in p)p.hasOwnProperty(B)&&R.push(encodeURIComponent(B)+"="+encodeURIComponent(p[B]));x+=R.join("&");a.id=j;a.style.width="100%";a.style.height="100%";a.frameBorder=0;a.webkitAllowFullScreen=true;a.mozAllowFullScreen=true;a.allowFullScreen=true;O.appendChild(a);a.src=x;i.addEventListener("message", +z,false)}else{c.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};A.dispatchEvent("error")}}},autoplay:{get:function(){return c.autoplay},set:function(x){c.autoplay=A._util.isAttributeSet(x)}},loop:{get:function(){return c.loop},set:function(x){c.loop=A._util.isAttributeSet(x)}},width:{get:function(){return A.parentNode.offsetWidth}},height:{get:function(){return A.parentNode.offsetHeight}},currentTime:{get:function(){return c.currentTime}, +set:function(x){v(x)}},duration:{get:function(){return c.duration}},ended:{get:function(){return c.ended}},paused:{get:function(){return c.paused}},seeking:{get:function(){return c.seeking}},readyState:{get:function(){return c.readyState}},networkState:{get:function(){return c.networkState}},volume:{get:function(){return c.muted>0?c.muted:c.volume},set:function(x){if(x<0||x>1)throw"Volume value must be between 0.0 and 1.0";g(x)}},muted:{get:function(){return c.muted>0},set:function(x){G(A._util.isAttributeSet(x))}}, +error:{get:function(){return c.error}}});A._canPlaySrc=m.HTMLVimeoVideoElement._canPlaySrc;A.canPlayType=m.HTMLVimeoVideoElement.canPlayType;return A}var f=16,h="",e="https://player.vimeo.com";m.HTMLVimeoVideoElement=function(n){return new b(n)};m.HTMLVimeoVideoElement._canPlaySrc=function(n){return/player.vimeo.com\/video\/\d+/.test(n)||/vimeo.com\/\d+/.test(n)?"probably":h};m.HTMLVimeoVideoElement.canPlayType=function(n){return n==="video/x-vimeo"?"probably":h}})(Popcorn,window,document);(function(m,i,k){function d(){var z;if(YT.loaded)for(v=true;o.length;){z=o.shift();z()}else setTimeout(d,250)}function b(){var z;if(!w){if(i.YT)d();else{z=k.createElement("script");z.addEventListener("load",d,false);z.src="https://www.youtube.com/iframe_api";k.head.appendChild(z)}w=true}return v}function f(z){o.push(z)}function h(z){function J(I){W.push(I)}function E(){Q.pauseVideo();j("play",E);l("play",K)}function g(){l("pause",N);j("pause",g)}function G(){var I=function(){if(Q.isMuted()){l("play", +c);Q.playVideo()}else setTimeout(I,0)};V=true;Q.mute();I()}function A(I){var P={name:"MediaError"};switch(I.data){case 2:P.message="Invalid video parameter.";P.code=MediaError.MEDIA_ERR_ABORTED;break;case 5:P.message="The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.";P.code=MediaError.MEDIA_ERR_DECODE;case 100:P.message="Video not found.";P.code=MediaError.MEDIA_ERR_NETWORK;break;case 101:case 150:P.message="Video not usable.";P.code= +MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;break;default:P.message="Unknown error.";P.code=5}F.error=P;H.dispatchEvent("error")}function O(){l("play",K);l("pause",N);if(F.autoplay||!F.paused){j("play",O);F.paused=false;J(function(){F.paused||K()})}F.muted||Q.unMute();F.readyState=H.HAVE_METADATA;H.dispatchEvent("loadedmetadata");$=setInterval(r,e);H.dispatchEvent("loadeddata");F.readyState=H.HAVE_FUTURE_DATA;H.dispatchEvent("canplay");U=true;for(aa=setInterval(M,50);W.length;){W[0]();W.shift()}F.readyState= +H.HAVE_ENOUGH_DATA;H.dispatchEvent("canplaythrough")}function a(){j("pause",a);if(Q.getCurrentTime()>0)setTimeout(a,0);else if(F.autoplay||!F.paused){l("play",O);Q.playVideo()}else O()}function c(){j("play",c);if(Q.getCurrentTime()===0)setTimeout(c,0);else{l("pause",a);Q.seekTo(0);Q.pauseVideo()}}function l(I,P){H.addEventListener("youtube-"+I,P,false)}function j(I,P){H.removeEventListener("youtube-"+I,P,false)}function s(I){H.dispatchEvent("youtube-"+I)}function t(){F.networkState=H.NETWORK_LOADING; +var I=Q.getDuration();if(F.duration!==I){F.duration=I;H.dispatchEvent("durationchange")}H.dispatchEvent("waiting")}function y(I){switch(I.data){case YT.PlayerState.ENDED:s("ended");break;case YT.PlayerState.PLAYING:s("play");break;case YT.PlayerState.PAUSED:Q.getDuration()!==Q.getCurrentTime()&&s("pause");break;case YT.PlayerState.BUFFERING:s("buffering")}I.data!==YT.PlayerState.BUFFERING&&ba===YT.PlayerState.BUFFERING&&H.dispatchEvent("progress");ba=I.data}function D(I){if(H._canPlaySrc(I)){F.src= +I;if(b()){if(V)if(U){if(V&&Q){j("buffering",t);j("ended",C);j("play",K);j("pause",N);N();Y=U=false;F.currentTime=0;W=[];clearInterval($);clearInterval(aa);Q.stopVideo();Q.clearVideo();Q.destroy();T=k.createElement("div")}}else{J(function(){D(I)});return}L.appendChild(T);var P=H._util.parseUri(I).queryKey;delete P.v;F.autoplay=P.autoplay==="1"||F.autoplay;delete P.autoplay;F.loop=P.loop==="1"||F.loop;delete P.loop;P.rel=P.rel||0;P.modestbranding=P.modestbranding||1;P.iv_load_policy=P.iv_load_policy|| +3;P.disablekb=P.disablekb||1;P.showinfo=P.showinfo||0;var da=i.location.protocol==="file:"?"*":i.location.protocol+"//"+i.location.host;P.origin=P.origin||da;P.controls=P.controls||F.controls?2:0;F.controls=P.controls;P.wmode=P.wmode||"opaque";if(P.html5!==0)P.html5=1;I=u.exec(I)[1];Q=new YT.Player(T,{width:"100%",height:"100%",wmode:P.wmode,videoId:I,playerVars:P,events:{onReady:G,onError:A,onStateChange:y}});F.networkState=H.NETWORK_LOADING;H.dispatchEvent("loadstart");H.dispatchEvent("progress")}else f(function(){D(I)})}else{F.error= +{name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};H.dispatchEvent("error")}}function r(){var I=Q.getCurrentTime();if(F.seeking)q(I-F.currentTime)<1&&R();else{if(q(F.currentTime-I)>e){B();R()}F.currentTime=I}}function M(){var I=Q.getVideoLoadedFraction();if(I&&Z!==I){Z=I;H.dispatchEvent("progress")}}function x(I){if(I!==F.currentTime){F.currentTime=I;if(U){B();Q.seekTo(I)}else J(function(){B();Q.seekTo(I)})}}function p(){H.dispatchEvent("timeupdate")} +function B(){l("pause",g);j("pause",N);F.seeking=true;H.dispatchEvent("seeking")}function R(){F.ended=false;F.seeking=false;H.dispatchEvent("timeupdate");H.dispatchEvent("seeked");H.dispatchEvent("canplay");H.dispatchEvent("canplaythrough")}function K(){if(F.ended){x(0);F.ended=false}ca=setInterval(p,H._util.TIMEUPDATE_MS);F.paused=false;if(X){X=false;if(F.loop&&!Y||!F.loop){Y=true;H.dispatchEvent("play")}H.dispatchEvent("playing")}}function N(){F.paused=true;if(!X){X=true;clearInterval(ca);H.dispatchEvent("pause")}} +function C(){if(F.loop){x(0);H.play()}else{F.ended=true;N();l("play",E);j("play",K);H.dispatchEvent("timeupdate");H.dispatchEvent("ended")}}function S(I){F.muted=I;if(U){Q[I?"mute":"unMute"]();H.dispatchEvent("volumechange")}else J(function(){S(F.muted)})}if(!i.postMessage)throw"ERROR: HTMLYouTubeVideoElement requires window.postMessage";var H=new m._MediaElementProto,L=typeof z==="string"?k.querySelector(z):z,T=k.createElement("div"),F={src:n,networkState:H.NETWORK_EMPTY,readyState:H.HAVE_NOTHING, +seeking:false,autoplay:n,preload:n,controls:false,loop:false,poster:n,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},V=false,U=false,Y=false,Q,X=true,W=[],ba=-1,aa,Z=0,$,ca;H._eventNamespace=m.guid("HTMLYouTubeVideoElement::");H.parentNode=L;H._util.type="YouTube";l("buffering",t);l("ended",C);H.play=function(){F.paused=false;U?Q.playVideo():J(function(){H.play()})};H.pause=function(){F.paused=true;if(U){g();Q.pauseVideo()}else J(function(){H.pause()})};Object.defineProperties(H, +{src:{get:function(){return F.src},set:function(I){I&&I!==F.src&&D(I)}},autoplay:{get:function(){return F.autoplay},set:function(I){F.autoplay=H._util.isAttributeSet(I)}},loop:{get:function(){return F.loop},set:function(I){F.loop=H._util.isAttributeSet(I)}},width:{get:function(){return H.parentNode.offsetWidth}},height:{get:function(){return H.parentNode.offsetHeight}},currentTime:{get:function(){return F.currentTime},set:function(I){x(I)}},duration:{get:function(){return F.duration}},ended:{get:function(){return F.ended}}, +paused:{get:function(){return F.paused}},seeking:{get:function(){return F.seeking}},readyState:{get:function(){return F.readyState}},networkState:{get:function(){return F.networkState}},volume:{get:function(){return F.volume},set:function(I){if(I<0||I>1)throw"Volume value must be between 0.0 and 1.0";F.volume=I;if(U){Q.setVolume(F.volume*100);H.dispatchEvent("volumechange")}else J(function(){H.volume=I})}},muted:{get:function(){return F.muted},set:function(I){S(H._util.isAttributeSet(I))}},error:{get:function(){return F.error}}, +buffered:{get:function(){return{start:function(I){if(I===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(I){if(I===0){if(!F.duration)return 0;return F.duration*Z}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}},configurable:true}});H._canPlaySrc=m.HTMLYouTubeVideoElement._canPlaySrc;H.canPlayType=m.HTMLYouTubeVideoElement.canPlayType;return H}var e=10,n="",u=/^.*(?:\/|v=)(.{11})/,q=Math.abs,v=false,w=false,o=[];m.HTMLYouTubeVideoElement=function(z){return new h(z)};m.HTMLYouTubeVideoElement._canPlaySrc= +function(z){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu).*(?:\/|v=)(.{11})/.test(z)?"probably":n};m.HTMLYouTubeVideoElement.canPlayType=function(z){return z==="video/x-youtube"?"probably":n}})(Popcorn,window,document);(function(m){var i=function(k,d){var b=0,f=0,h;m.forEach(d.classes,function(e,n){h=[];if(e==="parent")h[0]=document.querySelectorAll("#"+d.target)[0].parentNode;else h=document.querySelectorAll("#"+d.target+" "+e);b=0;for(f=h.length;b"+E.title+"

";m.forEach(E.items,function(G,A){if(A=x.interval*(r+1)/1E3&&(D<=x.interval*(r+2)/1E3||D>=x.interval*M/1E3)){l.setPosition(new google.maps.LatLng(x.position.lat,x.position.lng));l.setPov({heading:x.pov.heading||y(x,s[r+1])||0,zoom:x.pov.zoom||0,pitch:x.pov.pitch|| +0})}}G(s,s[0].interval)}else{r=0;for(M=s.length;r=x*(r+1)/1E3&&(D<=x*(r+2)/1E3||D>=x*M/1E3)){A.setPov({heading:y(s[r],s[r+1])||0,zoom:z.zoom,pitch:z.pitch||0});A.setPosition(O[r])}}G(O,z.interval)}},t)};if(z.location&&typeof z.tween==="string"){var A=E,O=[],a=new google.maps.DirectionsService,c=new google.maps.DirectionsRenderer(A);a.route({origin:z.location,destination:z.tween,travelMode:google.maps.TravelMode.DRIVING},function(s,t){if(t==google.maps.DirectionsStatus.OK){c.setDirections(s); +for(var y=s.routes[0].overview_path,D=0,r=y.length;D-1){b.trackedContainer=i(h);b.trackedContainer.element.appendChild(b.anchor)}else h.appendChild(b.anchor); +f.addEventListener("load",function(){f.style.borderStyle="none";b.anchor.href=b.href||b.src||"#";b.anchor.target="_blank";var e,n;f.style.height=h.style.height;f.style.width=h.style.width;b.anchor.appendChild(f);if(b.text){e=f.height/12+"px";n=document.createElement("div");m.extend(n.style,{color:"black",fontSize:e,fontWeight:"bold",position:"relative",textAlign:"center",width:f.style.width||f.width+"px",zIndex:"10"});n.innerHTML=b.text||"";n.style.top=(f.style.height.replace("px","")||f.height)/ +2-n.offsetHeight/2+"px";b.anchor.insertBefore(n,f)}},false);f.src=b.src;b.toString=function(){var e=b.src||b._natives.manifest.options.src["default"],n=e.replace(/.*\//g,"");return n.length?n:e}},start:function(b,f){f.anchor.style.display="inline";f.trackedContainer&&f.trackedContainer.start()},end:function(b,f){f.anchor.style.display="none";f.trackedContainer&&f.trackedContainer.stop()},_teardown:function(b){if(b.trackedContainer)b.trackedContainer.destroy();else b.anchor.parentNode&&b.anchor.parentNode.removeChild(b.anchor)}})})(Popcorn);(function(m){var i=/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu|vimeo|soundcloud|baseplayer)/,k={},d={vimeo:false,youtube:false,soundcloud:false,module:false};Object.defineProperty(k,void 0,{get:function(){return d[void 0]},set:function(b){d[void 0]=b}});m.plugin("mediaspawner",{manifest:{about:{name:"Popcorn Media Spawner Plugin",version:"0.1",author:"Matthew Schranz, @mjschranz",website:"mschranz.wordpress.com"},options:{source:{elem:"input",type:"text",label:"Media Source","default":"http://www.youtube.com/watch?v=CXDstfD9eJ0"}, +caption:{elem:"input",type:"text",label:"Media Caption","default":"Popcorn Popping",optional:true},target:"mediaspawner-container",start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},autoplay:{elem:"input",type:"checkbox",label:"Autoplay Video",optional:true},width:{elem:"input",type:"number",label:"Media Width","default":400,units:"px",optional:true},height:{elem:"input",type:"number",label:"Media Height","default":200,units:"px",optional:true}}},_setup:function(b){function f(){function v(){if(n!== +"HTML5"&&!window.Popcorn[n])setTimeout(function(){v()},300);else{b.id=b._container.id;b._container.style.width=b.width+"px";b._container.style.height=b.height+"px";b.popcorn=m.smart("#"+b.id,b.source);n==="HTML5"&&b.popcorn.controls(true);b._container.style.width="0px";b._container.style.height="0px";b._container.style.visibility="hidden";b._container.style.overflow="hidden"}}if(n!=="HTML5"&&!window.Popcorn[n]&&!k[n]){k[n]=true;m.getScript("http://popcornjs.org/code/players/"+n+"/popcorn."+n+".js", +function(){v()})}else v()}function h(){window.Popcorn.player?f():setTimeout(function(){h()},300)}var e=document.getElementById(b.target)||{},n,u,q;if(u=i.exec(b.source)){n=u[1];if(n==="youtu")n="youtube"}else n="HTML5";b._type=n;b._container=document.createElement("div");u=b._container;u.id="mediaSpawnerdiv-"+m.guid();b.width=b.width||400;b.height=b.height||200;if(b.caption){q=document.createElement("div");q.innerHTML=b.caption;q.style.display="none";b._capCont=q;u.appendChild(q)}e&&e.appendChild(u); +if(!window.Popcorn.player&&!k.module){k.module=true;m.getScript("http://popcornjs.org/code/modules/player/popcorn.player.js",h)}else h();b.toString=function(){return b.source||b._natives.manifest.options.source["default"]}},start:function(b,f){if(f._capCont)f._capCont.style.display="";f._container.style.width=f.width+"px";f._container.style.height=f.height+"px";f._container.style.visibility="visible";f._container.style.overflow="visible";f.autoplay&&f.popcorn.play()},end:function(b,f){if(f._capCont)f._capCont.style.display= +"none";f._container.style.width="0px";f._container.style.height="0px";f._container.style.visibility="hidden";f._container.style.overflow="hidden";f.popcorn.pause()},_teardown:function(b){b.popcorn&&b.popcorn.destory&&b.popcorn.destroy();document.getElementById(b.target)&&document.getElementById(b.target).removeChild(b._container)}})})(Popcorn,this);(function(m){m.plugin("mustache",function(i){var k,d,b,f;m.getScript("http://mustache.github.com/extras/mustache.js");var h=!!i.dynamic,e=typeof i.template,n=typeof i.data,u=document.getElementById(i.target);i.container=u||document.createElement("div");if(e==="function")if(h)b=i.template;else f=i.template(i);else f=e==="string"?i.template:"";if(n==="function")if(h)k=i.data;else d=i.data(i);else d=n==="string"?JSON.parse(i.data):n==="object"?i.data:"";return{start:function(q,v){var w=function(){if(window.Mustache){if(k)d= +k(v);if(b)f=b(v);var o=Mustache.to_html(f,d).replace(/^\s*/mg,"");v.container.innerHTML=o}else setTimeout(function(){w()},10)};w()},end:function(q,v){v.container.innerHTML=""},_teardown:function(){k=d=b=f=null}}},{about:{name:"Popcorn Mustache Plugin",version:"0.1",author:"David Humphrey (@humphd)",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"mustache-container",template:{elem:"input",type:"text", +label:"Template"},data:{elem:"input",type:"text",label:"Data"},dynamic:{elem:"input",type:"checkbox",label:"Dynamic","default":true}}})})(Popcorn);(function(m){function i(d,b){if(d.map)d.map.div.style.display=b;else setTimeout(function(){i(d,b)},10)}var k=1;m.plugin("openmap",function(d){var b,f,h,e,n,u,q,v,w=document.getElementById(d.target);b=document.createElement("div");b.id="openmapdiv"+k;b.style.width="100%";b.style.height="100%";k++;w&&w.appendChild(b);v=function(){if(window.OpenLayers&&window.OpenLayers.Layer.Stamen){if(d.location){location=new OpenLayers.LonLat(0,0);m.getJSONP("//tinygeocoder.com/create-api.php?q="+d.location+"&callback=jsonp", +function(z){f=new OpenLayers.LonLat(z[1],z[0])})}else f=new OpenLayers.LonLat(d.lng,d.lat);d.type=d.type||"ROADMAP";switch(d.type){case "SATELLITE":d.map=new OpenLayers.Map({div:b,maxResolution:0.28125,tileSize:new OpenLayers.Size(512,512)});var o=new OpenLayers.Layer.WorldWind("LANDSAT","//worldwind25.arc.nasa.gov/tile/tile.aspx",2.25,4,{T:"105"});d.map.addLayer(o);e=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:4326");break;case "TERRAIN":e=new OpenLayers.Projection("EPSG:4326"); +h=new OpenLayers.Projection("EPSG:4326");d.map=new OpenLayers.Map({div:b,projection:h});o=new OpenLayers.Layer.WMS("USGS Terraserver","//terraserver-usa.org/ogcmap.ashx?",{layers:"DRG"});d.map.addLayer(o);break;case "STAMEN-TONER":case "STAMEN-WATERCOLOR":case "STAMEN-TERRAIN":o=d.type.replace("STAMEN-","").toLowerCase();o=new OpenLayers.Layer.Stamen(o);e=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:900913");f=f.transform(e,h);d.map=new OpenLayers.Map({div:b,projection:h, +displayProjection:e,controls:[new OpenLayers.Control.Navigation,new OpenLayers.Control.PanPanel,new OpenLayers.Control.ZoomPanel]});d.map.addLayer(o);break;default:h=new OpenLayers.Projection("EPSG:900913");e=new OpenLayers.Projection("EPSG:4326");f=f.transform(e,h);d.map=new OpenLayers.Map({div:b,projection:h,displayProjection:e});o=new OpenLayers.Layer.OSM;d.map.addLayer(o)}if(d.map){d.map.setCenter(f,d.zoom||10);d.map.div.style.display="none"}}else setTimeout(function(){v()},50)};v();return{_setup:function(o){window.OpenLayers|| +m.getScript("//openlayers.org/api/OpenLayers.js",function(){m.getScript("//maps.stamen.com/js/tile.stamen.js")});var z=function(){if(o.map){o.zoom=o.zoom||2;if(o.zoom&&typeof o.zoom!=="number")o.zoom=+o.zoom;o.map.setCenter(f,o.zoom);if(o.markers){var J=OpenLayers.Util.extend({},OpenLayers.Feature.Vector.style["default"]),E=function(j){clickedFeature=j.feature;if(clickedFeature.attributes.text){q=new OpenLayers.Popup.FramedCloud("featurePopup",clickedFeature.geometry.getBounds().getCenterLonLat(), +new OpenLayers.Size(120,250),clickedFeature.attributes.text,null,true,function(){u.unselect(this.feature)});clickedFeature.popup=q;q.feature=clickedFeature;o.map.addPopup(q)}},g=function(j){feature=j.feature;if(feature.popup){q.feature=null;o.map.removePopup(feature.popup);feature.popup.destroy();feature.popup=null}},G=function(j){m.getJSONP("//tinygeocoder.com/create-api.php?q="+j.location+"&callback=jsonp",function(s){s=(new OpenLayers.Geometry.Point(s[1],s[0])).transform(e,h);var t=OpenLayers.Util.extend({}, +J);if(!j.size||isNaN(j.size))j.size=14;t.pointRadius=j.size;t.graphicOpacity=1;t.externalGraphic=j.icon;s=new OpenLayers.Feature.Vector(s,null,t);if(j.text)s.attributes={text:j.text};n.addFeatures([s])})};n=new OpenLayers.Layer.Vector("Point Layer",{style:J});o.map.addLayer(n);for(var A=0,O=o.markers.length;A"']/g,function(f){return d[f]||f})}function k(b,f){var h=b.container=document.createElement("div"),e=h.style,n=b.media,u=function(){var q=b.position();e.fontSize="18px";e.width=n.offsetWidth+"px";e.top=q.top+n.offsetHeight-h.offsetHeight-40+"px";e.left=q.left+"px";setTimeout(u,10)};h.id=f||"";e.position="absolute";e.color="white";e.textShadow="black 2px 2px 6px";e.fontWeight="bold";e.textAlign="center";u();b.media.parentNode.appendChild(h); +return h}var d={"&":"&","<":"<",">":">",'"':""","'":"'"};m.plugin("text",{manifest:{about:{name:"Popcorn Text Plugin",version:"0.1",author:"@humphd"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},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(b){var f,h,e=b._container=document.createElement("div"); +e.style.display="none";if(b.target)if(f=m.dom.find(b.target)){if(["VIDEO","AUDIO"].indexOf(f.nodeName)>-1)f=k(this,b.target+"-overlay")}else f=k(this,b.target);else f=this.container?this.container:k(this);b._target=f;h=b.escape?i(b.text):b.text;h=b.multiline?h.replace(/\r?\n/gm,"
"):h;e.innerHTML=h||"";f.appendChild(e);b.toString=function(){return b.text||b._natives.manifest.options.text["default"]}},start:function(b,f){f._container.style.display="inline"},end:function(b,f){f._container.style.display= +"none"},_teardown:function(b){var f=b._target;f&&f.removeChild(b._container)}})})(Popcorn);(function(m){var i=1;m.plugin("timeline",function(k){var d=document.getElementById(k.target),b=document.createElement("div"),f,h=true;if(d&&!d.firstChild){d.appendChild(f=document.createElement("div"));f.style.width="inherit";f.style.height="inherit";f.style.overflow="auto"}else f=d.firstChild;b.style.display="none";b.id="timelineDiv"+i;k.direction=k.direction||"up";if(k.direction.toLowerCase()==="down")h=false;if(d&&f)h?f.insertBefore(b,f.firstChild):f.appendChild(b);i++;b.innerHTML="

"+ +k.title+"
"+k.text+"
"+k.innerHTML;return{start:function(e,n){b.style.display="block";if(n.direction==="down")f.scrollTop=f.scrollHeight},end:function(){b.style.display="none"},_teardown:function(){f&&b&&f.removeChild(b)&&!f.firstChild&&d.removeChild(f)}}},{about:{name:"Popcorn Timeline Plugin",version:"0.1",author:"David Seifried @dcseifried",website:"dseifried.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"}, +end:{elem:"input",type:"number",label:"End"},target:"feed-container",title:{elem:"input",type:"text",label:"Title"},text:{elem:"input",type:"text",label:"Text"},innerHTML:{elem:"input",type:"text",label:"HTML Code",optional:true},direction:{elem:"select",options:["DOWN","UP"],label:"Direction",optional:true}}})})(Popcorn);(function(m){m.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:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},src:{elem:"input",type:"url",label:"Webpage URL","default":"http://mozillapopcorn.org"},target:"iframe-container"}},_setup:function(i){var k=document.getElementById(i.target);i.src=i.src.replace(/^(https?:)?(\/\/)?/, +"//");i._iframe=document.createElement("iframe");i._iframe.setAttribute("width","100%");i._iframe.setAttribute("height","100%");i._iframe.id=i.id;i._iframe.src=i.src;i._iframe.style.display="none";k&&k.appendChild(i._iframe)},start:function(i,k){k._iframe.src=k.src;k._iframe.style.display="inline"},end:function(i,k){k._iframe.style.display="none"},_teardown:function(i){document.getElementById(i.target)&&document.getElementById(i.target).removeChild(i._iframe)}})})(Popcorn);var wikiCallback; +(function(m){m.plugin("wikipedia",{manifest:{about:{name:"Popcorn Wikipedia Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},lang:{elem:"input",type:"text",label:"Language","default":"english",optional:true},src:{elem:"input",type:"url",label:"Wikipedia URL","default":"http://en.wikipedia.org/wiki/Cat"},title:{elem:"input",type:"text",label:"Title","default":"Cats",optional:true}, +numberofwords:{elem:"input",type:"number",label:"Number of Words","default":"200",optional:true},target:"wikipedia-container"}},_setup:function(i){var k,d=m.guid();if(!i.lang)i.lang="en";i.numberofwords=i.numberofwords||200;window["wikiCallback"+d]=function(b){i._link=document.createElement("a");i._link.setAttribute("href",i.src);i._link.setAttribute("target","_blank");i._link.innerHTML=i.title||b.parse.displaytitle;i._desc=document.createElement("p");k=b.parse.text["*"].substr(b.parse.text["*"].indexOf("

")); +k=k.replace(/((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g,"");k=k.split(" ");i._desc.innerHTML=k.slice(0,k.length>=i.numberofwords?i.numberofwords:k.length).join(" ")+" ...";i._fired=true};i.src&&m.getScript("//"+i.lang+".wikipedia.org/w/api.php?action=parse&props=text&redirects&page="+i.src.slice(i.src.lastIndexOf("/")+1)+"&format=json&callback=wikiCallback"+d);i.toString=function(){return i.src||i._natives.manifest.options.src["default"]}},start:function(i,k){var d=function(){if(k._fired){if(k._link&& +k._desc)if(document.getElementById(k.target)){document.getElementById(k.target).appendChild(k._link);document.getElementById(k.target).appendChild(k._desc);k._added=true}}else setTimeout(function(){d()},13)};d()},end:function(i,k){if(k._added){document.getElementById(k.target).removeChild(k._link);document.getElementById(k.target).removeChild(k._desc)}},_teardown:function(i){if(i._added){i._link.parentNode&&document.getElementById(i.target).removeChild(i._link);i._desc.parentNode&&document.getElementById(i.target).removeChild(i._desc); +delete i.target}}})})(Popcorn);(function(m){var i={},k=0,d=document.createElement("span"),b=["webkit","Moz","ms","O",""],f=["Transform","TransitionDuration","TransitionTimingFunction"],h={},e;document.getElementsByTagName("head")[0].appendChild(d);for(var n=0,u=f.length;n");d.push(e("subtitle",n))}catch(v){for(;b< +f&&i[b];)b++}for(;b=0&&!u[v];)v--;w=v+1;for(e=0;e[\t ]*/);o.start=k(q[0]);n=q[1].indexOf(" ");if(n!==-1)q[1]=q[1].substr(0,n);for(o.end=k(q[1]);e/g,">");o.text=o.text.replace(/<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)>/gi,"<$1$3$7>");o.text=o.text.replace(/\\N/gi,"
");if(b&&b.target)o.target=b.target;h.push(i("subtitle", +o))}f.data=h;return f})})(Popcorn);(function(m){function i(b,f){var h=b.substr(10).split(","),e;e={start:k(h[f.start]),end:k(h[f.end])};if(e.start===-1||e.end===-1)throw"Invalid time";var n=w.call(q,/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,""),u=n.replace,q;q=h.length;w=[];for(var v=f.text;v");return e}function k(b){var f=b.split(":");if(b.length!==10||f.length<3)return-1;return parseInt(f[0],10)*3600+parseInt(f[1],10)*60+parseFloat(f[2],10)}function d(b, +f){var h={};h[b]=f;return h}m.parser("parseSSA",function(b){var f={title:"",remote:"",data:[]},h=[],e=0,n;b=b.text.split(/(?:\r\n|\r|\n)/gm);for(n=b.length;e");q.id=e.getAttribute("xml:id")||e.getAttribute("id");q.start=b(e.getAttribute("begin"),n); +q.end=b(e.getAttribute("end"),n);q.target=k(e,u);if(q.end<0){q.end=b(e.getAttribute("duration"),0);if(q.end>=0)q.end+=q.start;else q.end=Number.MAX_VALUE}return{subtitle:q}}function b(e,n){var u;if(!e)return-1;try{return m.util.toSeconds(e)}catch(q){for(var v=e.length-1;v>=0&&e[v]<="9"&&e[v]>="0";)v--;u=v;v=parseFloat(e.substring(0,u));u=e.substring(u);return v*({h:3600,m:60,s:1,ms:0.001}[u]||-1)+(n||0)}}var f=/^[\s]+|[\s]+$/gm,h=/(?:\r\n|\r|\n)/gm;m.parser("parseTTML",function(e){var n={title:"", +remote:"",data:[]};if(!e.xml||!e.xml.documentElement)return n;e=e.xml.documentElement.firstChild;if(!e)return n;for(;e.nodeName!=="body";)e=e.nextSibling;if(e)n.data=i(e,0);return n})})(Popcorn);(function(m){m.parser("parseTTXT",function(i){var k={title:"",remote:"",data:[]},d=function(n){n=n.split(":");var u=0;try{return parseFloat(n[0],10)*60*60+parseFloat(n[1],10)*60+parseFloat(n[2],10)}catch(q){u=0}return u},b=function(n,u){var q={};q[n]=u;return q};i=i.xml.lastChild.lastChild;for(var f=Number.MAX_VALUE,h=[];i;){if(i.nodeType===1&&i.nodeName==="TextSample"){var e={};e.start=d(i.getAttribute("sampleTime"));e.text=i.getAttribute("text");if(e.text){e.end=f-0.001;h.push(b("subtitle",e))}f= +e.start}i=i.previousSibling}k.data=h.reverse();return k})})(Popcorn);(function(m){function i(d){var b=d.split(":");d=d.length;var f;if(d!==12&&d!==9)throw"Bad cue";d=b.length-1;try{f=parseInt(b[d-1],10)*60+parseFloat(b[d],10);if(d===2)f+=parseInt(b[0],10)*3600}catch(h){throw"Bad cue";}return f}function k(d,b){var f={};f[d]=b;return f}m.parser("parseVTT",function(d){var b={title:"",remote:"",data:[]},f=[],h=0,e=0,n,u;d=d.text.split(/(?:\r\n|\r|\n)/gm);e=d.length;if(e===0||d[0]!=="WEBVTT")return b;for(h++;h")===-1)throw"Bad cue";q=v.replace(/--\>/," --\> ").split(/[\t ]+/);if(q.length<2)throw"Bad cue";w.id=v;w.start=i(q[0]);w.end=i(q[2]);for(u=w;h");f.push(k("subtitle",u))}catch(o){for(h=h;h 0; k-- ) { + if ( byEnd[ k ].end > newDuration ) { + self.removeTrackEvent( byEnd[ k ]._id ); + } + } + + // Remove any internal tracking of events that have end times greater than duration + // otherwise their end events will never be hit. + for ( var i = 0; i < byStart.length; i++ ) { + if ( byStart[ i ].end > newDuration ) { + self.removeTrackEvent( byStart[ i ]._id ); + } + } + + // References to byEnd/byStart are reset, so accessing it this way is + // forced upon us. + self.data.trackEvents.byEnd.push({ + start: newDurationPlus, + end: newDurationPlus + }); + + self.data.trackEvents.byStart.push({ + start: newDurationPlus, + end: newDurationPlus + }); + }; + + // Listen for duration changes and adjust internal tracking of event timings + self.media.addEventListener( "durationchange", self.data.durationChange, false ); + } + + 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 ); + } + } + }; + + self.media.addEventListener( "error", function() { + self.error = self.media.error; + }, false ); + + // http://www.whatwg.org/specs/web-apps/current-work/#dom-media-readystate + // + // If media is in readyState (rS) >= 1, we know the media's duration, + // which is required before running the isReady function. + // If rS is 0, attach a listener for "loadedmetadata", + // ( Which indicates that the media has moved from rS 0 to 1 ) + // + // This has been changed from a check for rS 2 because + // in certain conditions, Firefox can enter this code after dropping + // to rS 1 from a higher state such as 2 or 3. This caused a "loadeddata" + // listener to be attached to the media object, an event that had + // already triggered and would not trigger again. This left Popcorn with an + // instance that could never start a timeUpdate loop. + if ( self.media.readyState >= 1 ) { + + isReady(); + } else { + + self.media.addEventListener( "loadedmetadata", isReady, false ); + } + + return this; + } + }; + + // Extend constructor prototype to instance prototype + // Allows chaining methods to instances + Popcorn.p.init.prototype = Popcorn.p; + + Popcorn.byId = function( str ) { + var instances = Popcorn.instances, + length = instances.length, + i = 0; + + for ( ; i < length; i++ ) { + if ( instances[ i ].id === str ) { + return instances[ i ]; + } + } + + return null; + }; + + 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 ) { + + if ( !elem.parentNode ) { + return null; + } + + 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 ] ) { + return; + } + + instance.data.disabled[ plugin ] = true; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + 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 ); + + instance.emit( "trackend", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackend" + }) + ); + } + } + + return instance; + }, + enable: function( instance, plugin ) { + + if ( !instance.data.disabled[ plugin ] ) { + return; + } + + instance.data.disabled[ plugin ] = false; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + 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 ); + + instance.emit( "trackstart", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackstart", + track: event + }) + ); + } + } + + return instance; + }, + destroy: function( instance ) { + var events = instance.data.events, + trackEvents = instance.data.trackEvents, + 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 ); + } + + // Remove all data.trackEvents #1178 + trackEvents.byStart.length = 0; + trackEvents.byEnd.length = 0; + + if ( !instance.isDestroyed ) { + instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); + instance.isDestroyed = true; + } + + Popcorn.instances.splice( Popcorn.instances.indexOf( instance ), 1 ); + } + }); + + // 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 Math.round( this.media.currentTime ); + }, + + // Attach an event to a single point in time + exec: function( id, time, fn ) { + var length = arguments.length, + eventType = "trackadded", + trackEvent, sec, options; + + // Check if first could possibly be a SMPTE string + // p.cue( "smpte string", fn ); + // try/catch avoid awful throw in Popcorn.util.toSeconds + // TODO: Get rid of that, replace with NaN return? + try { + sec = Popcorn.util.toSeconds( id ); + } catch ( e ) {} + + // If it can be converted into a number then + // it's safe to assume that the string was SMPTE + if ( typeof sec === "number" ) { + id = sec; + } + + // Shift arguments based on use case + // + // Back compat for: + // p.cue( time, fn ); + if ( typeof id === "number" && length === 2 ) { + fn = time; + time = id; + id = Popcorn.guid( "cue" ); + } else { + // Support for new forms + + // p.cue( "empty-cue" ); + if ( length === 1 ) { + // Set a time for an empty cue. It's not important what + // the time actually is, because the cue is a no-op + time = -1; + + } else { + + // Get the TrackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + if ( trackEvent ) { + + // remove existing cue so a new one can be added via trackEvents.add + this.data.trackEvents.remove( id ); + TrackEvent.end( this, trackEvent ); + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + eventType = "cuechange"; + + // p.cue( "my-id", 12 ); + // p.cue( "my-id", function() { ... }); + if ( typeof id === "string" && length === 2 ) { + + // p.cue( "my-id", 12 ); + // The path will update the cue time. + if ( typeof time === "number" ) { + // Re-use existing TrackEvent start callback + fn = trackEvent._natives.start; + } + + // p.cue( "my-id", function() { ... }); + // The path will update the cue function + if ( typeof time === "function" ) { + fn = time; + // Re-use existing TrackEvent start time + time = trackEvent.start; + } + } + } else { + + if ( length >= 2 ) { + + // p.cue( "a", "00:00:00"); + if ( typeof time === "string" ) { + try { + sec = Popcorn.util.toSeconds( time ); + } catch ( e ) {} + + time = sec; + } + + // p.cue( "b", 11 ); + // p.cue( "b", 11, function() {} ); + if ( typeof time === "number" ) { + fn = fn || Popcorn.nop(); + } + + // p.cue( "c", function() {}); + if ( typeof time === "function" ) { + fn = time; + time = -1; + } + } + } + } + } + + options = { + id: id, + start: time, + end: time + 1, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "cue" + } + }; + + if ( trackEvent ) { + options = Popcorn.extend( trackEvent, options ); + } + + if ( eventType === "cuechange" ) { + + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, options ); + + this.emit( eventType, Popcorn.extend({}, options, { + id: id, + type: eventType, + previousValue: { + time: trackEvent.start, + fn: trackEvent._natives.start + }, + currentValue: { + time: time, + fn: fn || Popcorn.nop + }, + track: trackEvent + })); + } else { + // Creating a one second track event with an empty end + Popcorn.addTrackEvent( this, options ); + } + + 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, clonedEvents, + events = this.data.events[ type ]; + + // setup checks for custom event system + if ( events ) { + eventInterface = Popcorn.events.getInterface( type ); + + if ( eventInterface ) { + evt = document.createEvent( eventInterface ); + evt.initEvent( type, true, true, global, 1 ); + + this.media.dispatchEvent( evt ); + + return this; + } + + // clone events in case callbacks remove callbacks themselves + clonedEvents = events.slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( this, data ); + } + } + + return this; + }, + listen: function( type, fn ) { + var self = this, + hasEvents = true, + eventHook = Popcorn.events.hooks[ type ], + origType = type, + clonedEvents, + tmp; + + if ( typeof fn !== "function" ) { + throw new Error( "Popcorn.js Error: Listener is not a function" ); + } + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = []; + // Toggle if the previous assumption was untrue + 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 ].push( fn ); + + // only attach one event of any type + if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { + this.media.addEventListener( type, function( event ) { + if ( self.data.events[ type ] ) { + // clone events in case callbacks remove callbacks themselves + clonedEvents = self.data.events[ type ].slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( self, event ); + } + } + }, false ); + } + return this; + }, + unlisten: function( type, fn ) { + var ind, + events = this.data.events[ type ]; + + if ( !events ) { + return; // no listeners = nothing to do + } + + if ( typeof fn === "string" ) { + // legacy support for string-based removal -- not recommended + for ( var i = 0; i < events.length; i++ ) { + if ( events[ i ].name === fn ) { + // decrement i because array length just got smaller + events.splice( i--, 1 ); + } + } + + return this; + } else if ( typeof fn === "function" ) { + while( ind !== -1 ) { + ind = events.indexOf( fn ); + if ( ind !== -1 ) { + events.splice( ind, 1 ); + } + } + + return this; + } + + // if we got to this point, we are deleting all functions of this type + this.data.events[ type ] = null; + + return this; + } + }, + hooks: { + canplayall: { + bind: "canplaythrough", + add: function( event, callback ) { + + var state = false; + + if ( this.media.readyState ) { + + // always call canplayall asynchronously + setTimeout(function() { + callback.call( this, event ); + }.bind(this), 0 ); + + 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 - construct simple "TrackEvent" + // data type objects + function TrackEvent( track ) { + Abstract.put.call( this, track ); + } + + // Determine if a TrackEvent's "start" and "trackstart" must be called. + TrackEvent.start = function( instance, track ) { + + if ( track.end > instance.media.currentTime && + track.start <= instance.media.currentTime && !track._running ) { + + track._running = true; + instance.data.running[ track._natives.type ].push( track ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.start.call( instance, null, track ); + + instance.emit( "trackstart", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackstart", + track: track + }) + ); + } + } + }; + + // Determine if a TrackEvent's "end" and "trackend" must be called. + TrackEvent.end = function( instance, track ) { + + var runningPlugins; + + if ( ( track.end <= instance.media.currentTime || + track.start > instance.media.currentTime ) && track._running ) { + + runningPlugins = instance.data.running[ track._natives.type ]; + + track._running = false; + runningPlugins.splice( runningPlugins.indexOf( track ), 1 ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.end.call( instance, null, track ); + + instance.emit( "trackend", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackend", + track: track + }) + ); + } + } + }; + + // Internal Only - construct "TrackEvents" + // data type objects that are used by the Popcorn + // instance, stored at p.data.trackEvents + function TrackEvents( parent ) { + this.parent = parent; + + this.byStart = [{ + start: -1, + end: -1 + }]; + + this.byEnd = [{ + start: -1, + end: -1 + }]; + this.animating = []; + this.startIndex = 0; + this.endIndex = 0; + this.previousUpdateTime = -1; + + this.count = 1; + } + + function isMatch( obj, key, value ) { + return obj[ key ] && obj[ key ] === value; + } + + TrackEvents.prototype.where = function( params ) { + return ( this.parent.getTrackEvents() || [] ).filter(function( event ) { + var key, value; + + // If no explicit params, match all TrackEvents + if ( !params ) { + return true; + } + + // Filter keys in params against both the top level properties + // and the _natives properties + for ( key in params ) { + value = params[ key ]; + if ( isMatch( event, key, value ) || isMatch( event._natives, key, value ) ) { + return true; + } + } + return false; + }); + }; + + TrackEvents.prototype.add = function( track ) { + + // Store this definition in an array sorted by times + var byStart = this.byStart, + byEnd = this.byEnd, + startIndex, endIndex; + + // Push track event ids into the history + if ( track && track._id ) { + this.parent.data.history.push( track._id ); + } + + track.start = Popcorn.util.toSeconds( track.start, this.parent.options.framerate ); + track.end = Popcorn.util.toSeconds( track.end, this.parent.options.framerate ); + + 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; + } + } + + // update startIndex and endIndex + if ( startIndex <= this.parent.data.trackEvents.startIndex && + track.start <= this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.startIndex++; + } + + if ( endIndex <= this.parent.data.trackEvents.endIndex && + track.end < this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.endIndex++; + } + + this.count++; + + }; + + TrackEvents.prototype.remove = function( removeId, state ) { + + if ( removeId instanceof TrackEvent ) { + removeId = removeId.id; + } + + if ( typeof removeId === "object" ) { + // Filter by key=val and remove all matching TrackEvents + this.where( removeId ).forEach(function( event ) { + // |this| refers to the calling Popcorn "parent" instance + this.removeTrackEvent( event._id ); + }, this.parent ); + + return this; + } + + var start, end, animate, historyLen, track, + length = this.byStart.length, + index = 0, + indexWasAt = 0, + byStart = [], + byEnd = [], + animating = [], + history = [], + comparable = {}; + + state = state || {}; + + while ( --length > -1 ) { + start = this.byStart[ index ]; + end = this.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; + + // cache the track event being removed + track = 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 = this.animating.length; + index = 0; + + if ( length ) { + while ( --length > -1 ) { + animate = this.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 <= this.startIndex ) { + this.startIndex--; + } + + if ( indexWasAt <= this.endIndex ) { + this.endIndex--; + } + + this.byStart = byStart; + this.byEnd = byEnd; + this.animating = animating; + this.count--; + + historyLen = this.parent.data.history.length; + + for ( var i = 0; i < historyLen; i++ ) { + if ( this.parent.data.history[ i ] !== removeId ) { + history.push( this.parent.data.history[ i ] ); + } + } + + // Update ordered history array + this.parent.data.history = history; + + }; + + // Helper function used to retrieve old values of properties that + // are provided for update. + function getPreviousProperties( oldOptions, newOptions ) { + var matchProps = {}; + + for ( var prop in oldOptions ) { + if ( hasOwn.call( newOptions, prop ) && hasOwn.call( oldOptions, prop ) ) { + matchProps[ prop ] = oldOptions[ prop ]; + } + } + + return matchProps; + } + + // Internal Only - Adds track events to the instance object + Popcorn.addTrackEvent = function( obj, track ) { + var temp; + + if ( track instanceof TrackEvent ) { + return; + } + + track = new TrackEvent( 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 ] ) ) { + + // To ensure that the TrackEvent Invariant Policy is enforced, + // First, copy the properties of the newly created track event event + // to a temporary holder + temp = Popcorn.extend( {}, track ); + + // Next, copy the default onto the newly created trackevent, followed by the + // temporary holder. + Popcorn.extend( track, obj.options.defaults[ track._natives.type ], temp ); + } + + if ( track._natives ) { + // Supports user defined track event id + track._id = track.id || track._id || Popcorn.guid( track._natives.type ); + + // Trigger _setup method if exists + if ( track._natives._setup ) { + + track._natives._setup.call( obj, track ); + + obj.emit( "tracksetup", Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "tracksetup", + track: track + })); + } + } + + obj.data.trackEvents.add( track ); + TrackEvent.start( obj, track ); + + this.timeUpdate( obj, null, true ); + + // Store references to user added trackevents in ref table + if ( track._id ) { + Popcorn.addTrackEvent.ref( obj, track ); + } + + obj.emit( "trackadded", Popcorn.extend({}, track, + track._natives ? { plugin: track._natives.type } : {}, { + type: "trackadded", + track: 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 track = obj.getTrackEvent( removeId ); + + if ( !track ) { + return; + } + + // If a _teardown function was defined, + // enforce for track event removals + if ( track._natives._teardown ) { + track._natives._teardown.call( obj, track ); + } + + obj.data.trackEvents.remove( removeId ); + + // Update track event references + Popcorn.removeTrackEvent.ref( obj, removeId ); + + if ( track._natives ) { + + // Fire a trackremoved event + obj.emit( "trackremoved", Popcorn.extend({}, track, { + plugin: track._natives.type, + type: "trackremoved", + track: track + })); + } + }; + + // 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, + track: byEnd + }) + ); + } + } + + end++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + if ( byStart.end > currentTime && + byStart._running === false ) { + + 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, + track: byStart + }) + ); + } + } + 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, + track: byStart + }) + ); + } + } + start--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd.start <= currentTime && + byEnd._running === false ) { + + 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, + track: byEnd + }) + ); + } + } + 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 isfn = typeof definition === "function", + blacklist = [ "start", "end", "type", "manifest" ], + methods = [ "_setup", "_teardown", "start", "end", "frame" ], + plugin = {}, + setup; + + // 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 = options._natives.plugin = 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 ); + + args[ 1 ]._running = false; + this.emit( "trackend", + Popcorn.extend( {}, options, { + plugin: natives.type, + type: "trackend", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + }) + ); + }, natives._teardown ); + + // extend teardown to always trigger trackteardown after teardown + natives._teardown = combineFn( natives._teardown, function() { + + this.emit( "trackteardown", Popcorn.extend( {}, options, { + plugin: name, + type: "trackteardown", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + })); + }); + + // default to an empty string if no effect exists + // split string into an array of effects + options.compose = options.compose || []; + if ( typeof options.compose === "string" ) { + options.compose = options.compose.split( " " ); + } + options.effect = options.effect || []; + if ( typeof options.effect === "string" ) { + 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; + } + + if ( !options._id && options._natives ) { + // ensure an initial id is there before setup is called + options._id = Popcorn.guid( options._natives.type ); + } + + if ( options instanceof TrackEvent ) { + + if ( options._natives ) { + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + // Trigger _setup method if exists + if ( options._natives._setup ) { + + options._natives._setup.call( this, options ); + + this.emit( "tracksetup", Popcorn.extend( {}, options, { + plugin: options._natives.type, + type: "tracksetup", + track: options + })); + } + } + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + if ( options._id ) { + Popcorn.addTrackEvent.ref( this, options ); + } + } else { + // Create new track event for this instance + Popcorn.addTrackEvent( this, options ); + } + + // Future support for plugin event definitions + // for all of the native events + Popcorn.forEach( setup, function( callback, type ) { + // Don't attempt to create events for certain properties: + // "start", "end", "type", "manifest". Fixes #1365 + if ( blacklist.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( id, options ) { + var length = arguments.length, + trackEvent, defaults, mergedSetupOpts, previousOpts, newOpts; + + // Shift arguments based on use case + // + // Back compat for: + // p.plugin( options ); + if ( id && !options ) { + options = id; + id = null; + } else { + + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + // If the track event does not exist, ensure that the options + // object has a proper id + if ( !trackEvent ) { + options.id = id; + + // If the track event does exist, merge the updated properties + } else { + + newOpts = options; + previousOpts = getPreviousProperties( trackEvent, newOpts ); + + // Call the plugins defined update method if provided. Allows for + // custom defined updating for a track event to be defined by the plugin author + if ( trackEvent._natives._update ) { + + this.data.trackEvents.remove( trackEvent ); + + // It's safe to say that the intent of Start/End will never change + // Update them first before calling update + if ( hasOwn.call( options, "start" ) ) { + trackEvent.start = options.start; + } + + if ( hasOwn.call( options, "end" ) ) { + trackEvent.end = options.end; + } + + TrackEvent.end( this, trackEvent ); + + if ( isfn ) { + definition.call( this, trackEvent ); + } + + trackEvent._natives._update.call( this, trackEvent, options ); + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + } else { + // This branch is taken when there is no explicitly defined + // _update method for a plugin. Which will occur either explicitly or + // as a result of the plugin definition being a function that _returns_ + // a definition object. + // + // In either case, this path can ONLY be reached for TrackEvents that + // already exist. + + // Directly update the TrackEvent instance. + // This supports TrackEvent invariant enforcement. + Popcorn.extend( trackEvent, options ); + + this.data.trackEvents.remove( id ); + + // If a _teardown function was defined, + // enforce for track event removals + if ( trackEvent._natives._teardown ) { + trackEvent._natives._teardown.call( this, trackEvent ); + } + + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + if ( isfn ) { + pluginFn.call( this, definition.call( this, trackEvent ), trackEvent ); + } else { + + // Supports user defined track event id + trackEvent._id = trackEvent.id || trackEvent._id || Popcorn.guid( trackEvent._natives.type ); + + if ( trackEvent._natives && trackEvent._natives._setup ) { + + trackEvent._natives._setup.call( this, trackEvent ); + + this.emit( "tracksetup", Popcorn.extend( {}, trackEvent, { + plugin: trackEvent._natives.type, + type: "tracksetup", + track: trackEvent + })); + } + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, trackEvent ); + } + + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: trackEvent, + track: trackEvent + }); + + return this; + } + + if ( trackEvent._natives.type !== "cue" ) { + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: newOpts, + track: trackEvent + }); + } + + return this; + } + } + + this.data.running[ name ] = this.data.running[ name ] || []; + + // Merge with defaults if they exist, make sure per call is prioritized + defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {}; + mergedSetupOpts = Popcorn.extend( {}, defaults, options ); + + pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition, + mergedSetupOpts ); + + return this; + }; + + // 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; + + // 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 = { + ajax: null, + url: "", + data: "", + dataType: "", + success: Popcorn.nop, + type: "GET", + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8" + }; + + Popcorn.xhr = function( options ) { + var settings; + + 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; + } + + // Merge the "setup" defaults and custom "options" + // into a new plain object. + settings = Popcorn.extend( {}, setup, options ); + + // Create new XMLHttpRequest object + settings.ajax = new XMLHttpRequest(); + + 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; + } + + // Open the request + settings.ajax.open( settings.type, settings.url, settings.async ); + + // For POST, set the content-type request header + if ( settings.type === "POST" ) { + settings.ajax.setRequestHeader( + "Content-Type", settings.contentType + ); + } + + 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" ), + isFired = false, + params = [], + rjsonp = /(=)\?(?=&|$)|\?\?/, + replaceInUrl, prefix, paramStr, callback, callparam; + + if ( !isScript ) { + + // is there a calback already in the url + callparam = url.match( /(callback=[^&]*)/ ); + + if ( callparam !== null && callparam.length ) { + + prefix = callparam[ 1 ].split( "=" )[ 1 ]; + + // Since we need to support developer specified callbacks + // and placeholders in harmony, make sure matches to "callback=" + // aren't just placeholders. + // We coded ourselves into a corner here. + // JSONP callbacks should never have been + // allowed to have developer specified callbacks + if ( prefix === "?" ) { + prefix = "jsonp"; + } + + // get the callback name + callback = Popcorn.guid( prefix ); + + // replace existing callback name with unique callback name + url = url.replace( /(callback=[^&]*)/, "callback=" + callback ); + } else { + + callback = Popcorn.guid( "jsonp" ); + + if ( rjsonp.test( url ) ) { + url = url.replace( rjsonp, "$1" + callback ); + } + + // split on first question mark, + // this is to capture the query string + params = url.split( /\?(.+)?/ ); + + // rebuild url with callback + url = params[ 0 ] + "?"; + if ( params[ 1 ] ) { + url += params[ 1 ] + "&"; + } + url += "callback=" + callback; + } + + // Define the JSONP success callback globally + window[ callback ] = function( data ) { + // Fire success callbacks + success && success( data ); + isFired = true; + }; + } + + 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.addEventListener( "error", function( e ) { + // Handling remote script loading callbacks + success && success( { error: e } ); + + // Executing for JSONP requests + if ( !isScript ) { + // 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); +(function( global, Popcorn ) { + + var navigator = global.navigator; + + // Initialize locale data + // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes + function initLocale( arg ) { + + var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ), + parts = locale.split( "-" ); + + // Setup locale data table + return { + iso6391: locale, + language: parts[ 0 ] || "", + region: parts[ 1 ] || "" + }; + } + + // Declare locale data table + var localeData = initLocale( navigator.userLanguage || navigator.language ); + + Popcorn.locale = { + + // Popcorn.locale.get() + // returns reference to privately + // defined localeData + get: function() { + return localeData; + }, + + // Popcorn.locale.set( string|object ); + set: function( arg ) { + + localeData = initLocale( arg ); + + Popcorn.locale.broadcast(); + + return localeData; + }, + + // Popcorn.locale.broadcast( type ) + // Sends events to all popcorn media instances that are + // listening for locale events + broadcast: function( type ) { + + var instances = Popcorn.instances, + length = instances.length, + idx = 0, + instance; + + type = type || "locale:changed"; + + // Iterate all current instances + for ( ; idx < length; idx++ ) { + instance = instances[ idx ]; + + // For those instances with locale event listeners, + // trigger a locale change event + if ( type in instance.data.events ) { + instance.trigger( type ); + } + } + } + }; +})( this, this.Popcorn );(function( Popcorn ) { + + var + + AP = Array.prototype, + OP = Object.prototype, + + forEach = AP.forEach, + slice = AP.slice, + hasOwn = OP.hasOwnProperty, + toString = OP.toString; + + // stores parsers keyed on filetype + Popcorn.parsers = {}; + + // An interface for extending Popcorn + // with parser functionality + Popcorn.parser = function( name, type, definition ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // fixes parameters for overloaded function call + if ( typeof type === "function" && !definition ) { + definition = type; + type = ""; + } + + if ( typeof definition !== "function" || typeof type !== "string" ) { + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + + var natives = Popcorn.events.all, + parseFn, + parser = {}; + + parseFn = function( filename, callback, options ) { + + if ( !filename ) { + return this; + } + + // fixes parameters for overloaded function call + if ( typeof callback !== "function" && !options ) { + options = callback; + callback = null; + } + + var that = this; + + Popcorn.xhr({ + url: filename, + dataType: type, + success: function( data ) { + + var tracksObject = definition( data, options ), + tracksData, + tracksDataLen, + tracksDef, + idx = 0; + + tracksData = tracksObject.data || []; + tracksDataLen = tracksData.length; + tracksDef = null; + + // If no tracks to process, return immediately + if ( !tracksDataLen ) { + return; + } + + // Create tracks out of parsed object + for ( ; idx < tracksDataLen; idx++ ) { + + tracksDef = tracksData[ idx ]; + + for ( var key in tracksDef ) { + + if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) { + + that[ key ]( tracksDef[ key ] ); + } + } + } + if ( callback ) { + callback(); + } + } + }); + + return this; + }; + + // Assign new named definition + parser[ name ] = parseFn; + + // Extend Popcorn.p with new named definition + Popcorn.extend( Popcorn.p, parser ); + + // keys the function name by filetype extension + //Popcorn.parsers[ name ] = true; + + return parser; + }; +})( Popcorn ); +(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\-\_\.]+))$/; + + 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 { + + // Asynchronous so that users can catch this event + setTimeout( function() { + basePlayer.dispatchEvent( "error" ); + }, 0 ); + } + + 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 ](); + } + }; + }; + + // Popcorn.smart will attempt to find you a wrapper or player. If it can't do that, + // it will default to using an HTML5 video in the target. + Popcorn.smart = function( target, src, options ) { + var node = typeof target === "string" ? Popcorn.dom.find( target ) : target, + i, srci, j, media, mediaWrapper, popcorn, srcLength, + // We leave HTMLVideoElement and HTMLAudioElement wrappers out + // of the mix, since we'll default to HTML5 video if nothing + // else works. Waiting on #1254 before we add YouTube to this. + wrappers = "HTMLYouTubeVideoElement HTMLVimeoVideoElement HTMLSoundCloudAudioElement HTMLNullVideoElement".split(" "); + + if ( !node ) { + Popcorn.error( "Specified target `" + target + "` was not found." ); + return; + } + + // If our src is not an array, create an array of one. + src = typeof src === "string" ? [ src ] : src; + + // Loop through each src, and find the first playable. + for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { + srci = src[ i ]; + + // See if we can use a wrapper directly, if not, try players. + for ( j = 0; j < wrappers.length; j++ ) { + mediaWrapper = Popcorn[ wrappers[ j ] ]; + if ( mediaWrapper && mediaWrapper._canPlaySrc( srci ) === "probably" ) { + media = mediaWrapper( node ); + popcorn = Popcorn( media, options ); + // Set src, but not until after we return the media so the caller + // can get error events, if any. + setTimeout( function() { + media.src = srci; + }, 0 ); + return popcorn; + } + } + + // No wrapper can play this, check players. + for ( var key in Popcorn.player.registry ) { + if ( Popcorn.player.registry.hasOwnProperty( key ) ) { + if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, srci ) ) { + // Popcorn.smart( player, src, /* options */ ) + return Popcorn[ key ]( node, srci, options ); + } + } + } + } + + // If we don't have any players or wrappers that can handle this, + // Default to using HTML5 video. Similar to the HTMLVideoElement + // wrapper, we put a video in the div passed to us via: + // Popcorn.smart( div, src, options ) + var videoHTML, + videoElement, + videoID = Popcorn.guid( "popcorn-video-" ), + videoHTMLContainer = document.createElement( "div" ); + + videoHTMLContainer.style.width = "100%"; + videoHTMLContainer.style.height = "100%"; + + // If we only have one source, do not bother with source elements. + // This means we don't have the IE9 hack, + // and we can properly listen to error events. + // That way an error event can be told to backup to Flash if it fails. + if ( src.length === 1 ) { + videoElement = document.createElement( "video" ); + videoElement.id = videoID; + node.appendChild( videoElement ); + setTimeout( function() { + // Hack to decode html characters like & to & + var decodeDiv = document.createElement( "div" ); + decodeDiv.innerHTML = src[ 0 ]; + + videoElement.src = decodeDiv.firstChild.nodeValue; + }, 0 ); + return Popcorn( '#' + videoID, options ); + } + + node.appendChild( videoHTMLContainer ); + // IE9 doesn't like dynamic creation of source elements on

+

+ It seems a paradox, does it not, +

+
+ + + */ + + var rWhitespace = /^[\s]+|[\s]+$/gm, + rLineBreak = /(?:\r\n|\r|\n)/gm; + + Popcorn.parser( "parseTTML", function( data ) { + var returnData = { + title: "", + remote: "", + data: [] + }, + node; + + // Null checks + if ( !data.xml || !data.xml.documentElement ) { + return returnData; + } + + node = data.xml.documentElement.firstChild; + + if ( !node ) { + return returnData; + } + + // Find body tag + while ( node.nodeName !== "body" ) { + node = node.nextSibling; + } + + if ( node ) { + returnData.data = parseChildren( node, 0 ); + } + + return returnData; + }); + + // Parse the children of the given node + function parseChildren( node, timeOffset, region ) { + var currNode = node.firstChild, + currRegion = getNodeRegion( node, region ), + retVal = [], + newOffset; + + while ( currNode ) { + if ( currNode.nodeType === 1 ) { + if ( currNode.nodeName === "p" ) { + // p is a textual node, process contents as subtitle + retVal.push( parseNode( currNode, timeOffset, currRegion ) ); + } else if ( currNode.nodeName === "div" ) { + // div is container for subtitles, recurse + newOffset = toSeconds( currNode.getAttribute( "begin" ) ); + + if (newOffset < 0 ) { + newOffset = timeOffset; + } + + retVal.push.apply( retVal, parseChildren( currNode, newOffset, currRegion ) ); + } + } + + currNode = currNode.nextSibling; + } + + return retVal; + } + + // Get the "region" attribute of a node, to know where to put the subtitles + function getNodeRegion( node, defaultTo ) { + var region = node.getAttribute( "region" ); + + if ( region !== null ) { + return region; + } else { + return defaultTo || ""; + } + } + + // Parse a node for text content + function parseNode( node, timeOffset, region ) { + var sub = {}; + + // Trim left and right whitespace from text and convert non-explicit line breaks + sub.text = ( node.textContent || node.text ).replace( rWhitespace, "" ).replace( rLineBreak, "
" ); + sub.id = node.getAttribute( "xml:id" ) || node.getAttribute( "id" ); + sub.start = toSeconds ( node.getAttribute( "begin" ), timeOffset ); + sub.end = toSeconds( node.getAttribute( "end" ), timeOffset ); + sub.target = getNodeRegion( node, region ); + + if ( sub.end < 0 ) { + // No end given, infer duration if possible + // Otherwise, give end as MAX_VALUE + sub.end = toSeconds( node.getAttribute( "duration" ), 0 ); + + if ( sub.end >= 0 ) { + sub.end += sub.start; + } else { + sub.end = Number.MAX_VALUE; + } + } + + return { subtitle : sub }; + } + + // Convert time expression to SS.mmm + // Expression may be absolute to timeline (hh:mm:ss.ms) + // or relative ( decimal followed by metric ) ex: 3.4s, 5.7m + // Returns -1 if invalid + function toSeconds( t_in, offset ) { + var i; + + if ( !t_in ) { + return -1; + } + + try { + return Popcorn.util.toSeconds( t_in ); + } catch ( e ) { + i = getMetricIndex( t_in ); + return parseFloat( t_in.substring( 0, i ) ) * getMultipler( t_in.substring( i ) ) + ( offset || 0 ); + } + } + + // In a time string such as 3.4ms, get the index of the first character (m) of the time metric (ms) + function getMetricIndex( t_in ) { + var i = t_in.length - 1; + + while ( i >= 0 && t_in[ i ] <= "9" && t_in[ i ] >= "0" ) { + i--; + } + + return i; + } + + // Determine multiplier for metric relative to seconds + function getMultipler( metric ) { + return { + "h" : 3600, + "m" : 60, + "s" : 1, + "ms" : 0.001 + }[ metric ] || -1; + } +})( Popcorn ); +// PARSER: 0.1 TTXT + +(function (Popcorn) { + + /** + * TTXT popcorn parser plug-in + * Parses subtitle files in the TTXT format. + * Style information is ignored. + * Data parameter is given by Popcorn, will need an xml. + * Xml is the file contents to be parsed as a DOM tree + * + * @param {Object} data + * + * Example: + + */ + Popcorn.parser( "parseTTXT", function( data ) { + + // declare needed variables + var returnData = { + title: "", + remote: "", + data: [] + }; + + // Simple function to convert HH:MM:SS.MMM to SS.MMM + // Assume valid, returns 0 on error + var toSeconds = function(t_in) { + var t = t_in.split(":"); + var time = 0; + + try { + return parseFloat(t[0], 10)*60*60 + parseFloat(t[1], 10)*60 + parseFloat(t[2], 10); + } catch (e) { time = 0; } + + return time; + }; + + // creates an object of all atrributes keyed by name + var createTrack = function( name, attributes ) { + var track = {}; + track[name] = attributes; + return track; + }; + + // this is where things actually start + var node = data.xml.lastChild.lastChild; // Last Child of TextStreamHeader + var lastStart = Number.MAX_VALUE; + var cmds = []; + + // Work backwards through DOM, processing TextSample nodes + while (node) { + if ( node.nodeType === 1 && node.nodeName === "TextSample") { + var sub = {}; + sub.start = toSeconds(node.getAttribute('sampleTime')); + sub.text = node.getAttribute('text'); + + if (sub.text) { // Only process if text to display + // Infer end time from prior element, ms accuracy + sub.end = lastStart - 0.001; + cmds.push( createTrack("subtitle", sub) ); + } + lastStart = sub.start; + } + node = node.previousSibling; + } + + returnData.data = cmds.reverse(); + + return returnData; + }); + +})( Popcorn ); +// PARSER: 0.3 WebSRT/VTT + +(function ( Popcorn ) { + /** + * WebVTT popcorn parser plug-in + * Parses subtitle files in the WebVTT format. + * Specification here: http://www.whatwg.org/specs/web-apps/current-work/webvtt.html + * Styles which appear after timing information are presently ignored. + * Inline styling tags follow HTML conventions and are left in for the browser to handle (or ignore if VTT-specific) + * Data parameter is given by Popcorn, text property holds file contents. + * Text is the file contents to be parsed + * + * @param {Object} data + * + * Example: + 00:32.500 --> 00:00:33.500 A:start S:50% D:vertical L:98% + Laughs + */ + Popcorn.parser( "parseVTT", function( data ) { + + // declare needed variables + var retObj = { + title: "", + remote: "", + data: [] + }, + subs = [], + i = 0, + len = 0, + lines, + text, + sub, + rNewLine = /(?:\r\n|\r|\n)/gm; + + // Here is where the magic happens + // Split on line breaks + lines = data.text.split( rNewLine ); + len = lines.length; + + // Check for BOF token + if ( len === 0 || lines[ 0 ] !== "WEBVTT" ) { + return retObj; + } + + i++; + + while ( i < len ) { + text = []; + + try { + i = skipWhitespace( lines, len, i ); + sub = parseCueHeader( lines[ i++ ] ); + + // Build single line of text from multi-line subtitle in file + while ( i < len && lines[ i ] ) { + text.push( lines[ i++ ] ); + } + + // Join lines together to one and build subtitle text + sub.text = text.join( "
" ); + subs.push( createTrack( "subtitle", sub ) ); + } catch ( e ) { + i = skipNonWhitespace( lines, len, i ); + } + } + + retObj.data = subs; + return retObj; + }); + + // [HH:]MM:SS.mmm string to SS.mmm float + // Throws exception if invalid + function toSeconds ( t_in ) { + var t = t_in.split( ":" ), + l = t_in.length, + time; + + // Invalid time string provided + if ( l !== 12 && l !== 9 ) { + throw "Bad cue"; + } + + l = t.length - 1; + + try { + time = parseInt( t[ l-1 ], 10 ) * 60 + parseFloat( t[ l ], 10 ); + + // Hours were given + if ( l === 2 ) { + time += parseInt( t[ 0 ], 10 ) * 3600; + } + } catch ( e ) { + throw "Bad cue"; + } + + return time; + } + + function createTrack( name, attributes ) { + var track = {}; + track[ name ] = attributes; + return track; + } + + function parseCueHeader ( line ) { + var lineSegments, + args, + sub = {}, + rToken = /-->/, + rWhitespace = /[\t ]+/; + + if ( !line || line.indexOf( "-->" ) === -1 ) { + throw "Bad cue"; + } + + lineSegments = line.replace( rToken, " --> " ).split( rWhitespace ); + + if ( lineSegments.length < 2 ) { + throw "Bad cue"; + } + + sub.id = line; + sub.start = toSeconds( lineSegments[ 0 ] ); + sub.end = toSeconds( lineSegments[ 2 ] ); + + return sub; + } + + function skipWhitespace ( lines, len, i ) { + while ( i < len && !lines[ i ] ) { + i++; + } + + return i; + } + + function skipNonWhitespace ( lines, len, i ) { + while ( i < len && lines[ i ] ) { + i++; + } + + return i; + } +})( Popcorn ); +// PARSER: 0.1 XML + +(function (Popcorn) { + + /** + * + * + */ + Popcorn.parser( "parseXML", "XML", function( data ) { + + // declare needed variables + var returnData = { + title: "", + remote: "", + data: [] + }, + manifestData = {}; + + // Simple function to convert 0:05 to 0.5 in seconds + // acceptable formats are HH:MM:SS:MM, MM:SS:MM, SS:MM, SS + var toSeconds = function(time) { + var t = time.split(":"); + if (t.length === 1) { + return parseFloat(t[0], 10); + } else if (t.length === 2) { + return parseFloat(t[0], 10) + parseFloat(t[1] / 12, 10); + } else if (t.length === 3) { + return parseInt(t[0] * 60, 10) + parseFloat(t[1], 10) + parseFloat(t[2] / 12, 10); + } else if (t.length === 4) { + return parseInt(t[0] * 3600, 10) + parseInt(t[1] * 60, 10) + parseFloat(t[2], 10) + parseFloat(t[3] / 12, 10); + } + }; + + // turns a node tree element into a straight up javascript object + // also converts in and out to start and end + // also links manifest data with ids + var objectifyAttributes = function ( nodeAttributes ) { + + var returnObject = {}; + + for ( var i = 0, nal = nodeAttributes.length; i < nal; i++ ) { + + var key = nodeAttributes.item(i).nodeName, + data = nodeAttributes.item(i).nodeValue, + manifestItem = manifestData[ data ]; + + // converts in into start + if (key === "in") { + returnObject.start = toSeconds( data ); + // converts out into end + } else if ( key === "out" ){ + returnObject.end = toSeconds( data ); + // this is where ids in the manifest are linked + } else if ( key === "resourceid" ) { + for ( var item in manifestItem ) { + if ( manifestItem.hasOwnProperty( item ) ) { + if ( !returnObject[ item ] && item !== "id" ) { + returnObject[ item ] = manifestItem[ item ]; + } + } + } + // everything else + } else { + returnObject[key] = data; + } + + } + + return returnObject; + }; + + // creates an object of all atrributes keyd by name + var createTrack = function( name, attributes ) { + var track = {}; + track[name] = attributes; + return track; + }; + + // recursive function to process a node, or process the next child node + var parseNode = function ( node, allAttributes, manifest ) { + var attributes = {}; + Popcorn.extend( attributes, allAttributes, objectifyAttributes( node.attributes ), { text: node.textContent || node.text } ); + + var childNodes = node.childNodes; + + // processes the node + if ( childNodes.length < 1 || ( childNodes.length === 1 && childNodes[0].nodeType === 3 ) ) { + + if ( !manifest ) { + returnData.data.push( createTrack( node.nodeName, attributes ) ); + } else { + manifestData[attributes.id] = attributes; + } + + // process the next child node + } else { + + for ( var i = 0; i < childNodes.length; i++ ) { + + if ( childNodes[i].nodeType === 1 ) { + parseNode( childNodes[i], attributes, manifest ); + } + + } + } + }; + + // this is where things actually start + var x = data.documentElement.childNodes; + + for ( var i = 0, xl = x.length; i < xl; i++ ) { + + if ( x[i].nodeType === 1 ) { + + // start the process of each main node type, manifest or timeline + if ( x[i].nodeName === "manifest" ) { + parseNode( x[i], {}, true ); + } else { // timeline + parseNode( x[i], {}, false ); + } + + } + } + + return returnData; + }); + +})( Popcorn ); +(function( window, Popcorn ) { + + Popcorn.player( "soundcloud", { + _canPlayType: function( nodeName, url ) { + return ( typeof url === "string" && + Popcorn.HTMLSoundCloudAudioElement._canPlaySrc( url ) && + nodeName.toLowerCase() !== "audio" ); + } + }); + + Popcorn.soundcloud = function( container, url, options ) { + if ( typeof console !== "undefined" && console.warn ) { + console.warn( "Deprecated player 'soundcloud'. Please use Popcorn.HTMLSoundCloudAudioElement directly." ); + } + + var media = Popcorn.HTMLSoundCloudAudioElement( container ), + popcorn = Popcorn( media, options ); + + // Set the src "soon" but return popcorn instance first, so + // the caller can get get error events. + setTimeout( function() { + media.src = url; + }, 0 ); + + return popcorn; + }; + +}( window, Popcorn )); +(function( window, Popcorn ) { + + Popcorn.player( "vimeo", { + _canPlayType: function( nodeName, url ) { + return ( typeof url === "string" && + Popcorn.HTMLVimeoVideoElement._canPlaySrc( url ) ); + } + }); + + Popcorn.vimeo = function( container, url, options ) { + if ( typeof console !== "undefined" && console.warn ) { + console.warn( "Deprecated player 'vimeo'. Please use Popcorn.HTMLVimeoVideoElement directly." ); + } + + var media = Popcorn.HTMLVimeoVideoElement( container ), + popcorn = Popcorn( media, options ); + + // Set the src "soon" but return popcorn instance first, so + // the caller can get get error events. + setTimeout( function() { + media.src = url; + }, 0 ); + + return popcorn; + }; + +}( window, Popcorn )); +(function( window, Popcorn ) { + + var canPlayType = function( nodeName, url ) { + return ( typeof url === "string" && + Popcorn.HTMLYouTubeVideoElement._canPlaySrc( url ) ); + }; + + Popcorn.player( "youtube", { + _canPlayType: canPlayType + }); + + Popcorn.youtube = function( container, url, options ) { + if ( typeof console !== "undefined" && console.warn ) { + console.warn( "Deprecated player 'youtube'. Please use Popcorn.HTMLYouTubeVideoElement directly." ); + } + + var media = Popcorn.HTMLYouTubeVideoElement( container ), + popcorn = Popcorn( media, options ); + + // Set the src "soon" but return popcorn instance first, so + // the caller can listen for error events. + setTimeout( function() { + media.src = url; + }, 0 ); + + return popcorn; + }; + + Popcorn.youtube.canPlayType = canPlayType; + +}( window, Popcorn )); +// EFFECT: applyclass + +(function (Popcorn) { + + /** + * apply css class to jquery selector + * selector is relative to plugin target's id + * so .overlay is actually jQuery( "#target .overlay") + * + * @param {Object} options + * + * Example: + var p = Popcorn('#video') + .footnote({ + start: 5, // seconds + end: 15, // seconds + text: 'This video made exclusively for drumbeat.org', + target: 'footnotediv', + effect: 'applyclass', + applyclass: 'selector: class' + }) + * + */ + + var toggleClass = function( event, options ) { + + var idx = 0, len = 0, elements; + + Popcorn.forEach( options.classes, function( key, val ) { + + elements = []; + + if ( key === "parent" ) { + + elements[ 0 ] = document.querySelectorAll("#" + options.target )[ 0 ].parentNode; + } else { + + elements = document.querySelectorAll("#" + options.target + " " + key ); + } + + for ( idx = 0, len = elements.length; idx < len; idx++ ) { + + elements[ idx ].classList.toggle( val ); + } + }); + }; + + Popcorn.compose( "applyclass", { + + manifest: { + about: { + name: "Popcorn applyclass Effect", + version: "0.1", + author: "@scottdowne", + website: "scottdowne.wordpress.com" + }, + options: {} + }, + _setup: function( options ) { + + options.classes = {}; + options.applyclass = options.applyclass || ""; + + var classes = options.applyclass.replace( /\s/g, "" ).split( "," ), + item = [], + idx = 0, len = classes.length; + + for ( ; idx < len; idx++ ) { + + item = classes[ idx ].split( ":" ); + + if ( item[ 0 ] ) { + options.classes[ item[ 0 ] ] = item[ 1 ] || ""; + } + } + }, + start: toggleClass, + end: toggleClass + }); +})( Popcorn ); diff --git a/dist/popcorn-wrappers.min.js b/dist/popcorn-wrappers.min.js new file mode 100644 index 000000000..c41d90795 --- /dev/null +++ b/dist/popcorn-wrappers.min.js @@ -0,0 +1,138 @@ +/* + * popcorn.js version d05323f + * http://popcornjs.org + * + * Copyright 2011, Mozilla Foundation + * Licensed under the MIT license + */ + +(function(j,t){function s(a){H.put.call(this,a)}function m(a){this.parent=a;this.byStart=[{start:-1,end:-1}];this.byEnd=[{start:-1,end:-1}];this.animating=[];this.endIndex=this.startIndex=0;this.previousUpdateTime=-1;this.count=1}function g(a,b){return function(){if(c.plugin.debug)return a.apply(this,arguments);try{return a.apply(this,arguments)}catch(e){c.plugin.errors.push({plugin:b,thrown:e,source:a.toString()});this.emit("pluginerror",c.plugin.errors)}}}if(t.addEventListener){var p=Array.prototype, +i=Object.prototype,h=p.forEach,v=p.slice,A=i.hasOwnProperty,n=i.toString,u=j.Popcorn,B=[],w=false,E={events:{hash:{},apis:{}}},L=function(){return j.requestAnimationFrame||j.webkitRequestAnimationFrame||j.mozRequestAnimationFrame||j.oRequestAnimationFrame||j.msRequestAnimationFrame||function(a){j.setTimeout(a,16)}}(),H={put:function(a){for(var b in a)if(a.hasOwnProperty(b))this[b]=a[b]}},c=function(a,b){return new c.p.init(a,b||null)};c.version="d05323f";c.isSupported=true;c.instances=[];c.p=c.prototype= +{init:function(a,b){var e,d=this;if(typeof a==="function")if(t.readyState==="complete")a(t,c);else{B.push(a);if(!w){w=true;var o=function(){t.removeEventListener("DOMContentLoaded",o,false);for(var C=0,k=B.length;C0;z--)M[z].end>x&&d.removeTrackEvent(M[z]._id);for(M=0;Mx&&d.removeTrackEvent(J[M]._id);d.data.trackEvents.byEnd.push({start:R,end:R});d.data.trackEvents.byStart.push({start:R,end:R})};d.media.addEventListener("durationchange", +d.data.durationChange,false)}if(d.options.frameAnimation){d.data.timeUpdate=function(){c.timeUpdate(d,{});c.forEach(c.manifest,function(x,R){if(k=d.data.running[R]){r=k.length;for(var J=0;J=1?q():d.media.addEventListener("loadedmetadata",q,false);return this}}};c.p.init.prototype=c.p;c.byId=function(a){for(var b=c.instances,e=b.length,d=0;d=0;e--){d=a.data.running[b][e];d._natives.end.call(a, +null,d);a.emit("trackend",c.extend({},d,{plugin:d.type,type:"trackend"}))}return a}},enable:function(a,b){if(a.data.disabled[b]){a.data.disabled[b]=false;if(b in c.registryByName&&a.data.running[b])for(var e=a.data.running[b].length-1,d;e>=0;e--){d=a.data.running[b][e];d._natives.start.call(a,null,d);a.emit("trackstart",c.extend({},d,{plugin:d.type,type:"trackstart",track:d}))}return a}},destroy:function(a){var b=a.data.events,e=a.data.trackEvents,d,o,l,q;for(o in b){d=b[o];for(l in d)delete d[l]; +b[o]=null}for(q in c.registryByName)c.removePlugin(a,q);e.byStart.length=0;e.byEnd.length=0;if(!a.isDestroyed){a.data.timeUpdate&&a.media.removeEventListener("timeupdate",a.data.timeUpdate,false);a.isDestroyed=true}c.instances.splice(c.instances.indexOf(a),1)}});c.guid.counter=1;c.extend(c.p,function(){var a={};c.forEach("load play pause currentTime playbackRate volume duration preload playbackRate autoplay loop controls muted buffered readyState seeking paused played seekable ended".split(/\s+/g), +function(b){a[b]=function(e){var d;if(typeof this.media[b]==="function"){if(e!=null&&/play|pause/.test(b))this.media.currentTime=c.util.toSeconds(e);this.media[b]();return this}if(e!=null){d=this.media[b];this.media[b]=e;d!==e&&this.emit("attrchange",{attribute:b,previousValue:d,currentValue:e});return this}return this.media[b]}});return a}());c.forEach("enable disable".split(" "),function(a){c.p[a]=function(b){return c[a](this,b)}});c.extend(c.p,{roundTime:function(){return Math.round(this.media.currentTime)}, +exec:function(a,b,e){var d=arguments.length,o="trackadded",l,q;try{q=c.util.toSeconds(a)}catch(C){}if(typeof q==="number")a=q;if(typeof a==="number"&&d===2){e=b;b=a;a=c.guid("cue")}else if(d===1)b=-1;else if(l=this.getTrackEvent(a)){this.data.trackEvents.remove(a);s.end(this,l);c.removeTrackEvent.ref(this,a);o="cuechange";if(typeof a==="string"&&d===2){if(typeof b==="number")e=l._natives.start;if(typeof b==="function"){e=b;b=l.start}}}else if(d>=2){if(typeof b==="string"){try{q=c.util.toSeconds(b)}catch(k){}b= +q}if(typeof b==="number")e=e||c.nop();if(typeof b==="function"){e=b;b=-1}}d={id:a,start:b,end:b+1,_running:false,_natives:{start:e||c.nop,end:c.nop,type:"cue"}};if(l)d=c.extend(l,d);if(o==="cuechange"){d._id=d.id||d._id||c.guid(d._natives.type);this.data.trackEvents.add(d);s.start(this,d);this.timeUpdate(this,null,true);c.addTrackEvent.ref(this,d);this.emit(o,c.extend({},d,{id:a,type:o,previousValue:{time:l.start,fn:l._natives.start},currentValue:{time:b,fn:e||c.nop},track:l}))}else c.addTrackEvent(this, +d);return this},mute:function(a){a=a==null||a===true?"muted":"unmuted";if(a==="unmuted"){this.media.muted=false;this.media.volume=this.data.state.volume}if(a==="muted"){this.data.state.volume=this.media.volume;this.media.muted=true}this.emit(a);return this},unmute:function(a){return this.mute(a==null?false:!a)},position:function(){return c.position(this.media)},toggle:function(a){return c[this.data.disabled[a]?"enable":"disable"](this,a)},defaults:function(a,b){if(c.isArray(a)){c.forEach(a,function(e){for(var d in e)this.defaults(d, +e[d])},this);return this}if(!this.options.defaults)this.options.defaults={};this.options.defaults[a]||(this.options.defaults[a]={});c.extend(this.options.defaults[a],b);return this}});c.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"}; +c.Events.Natives=c.Events.UIEvents+" "+c.Events.MouseEvents+" "+c.Events.Events;E.events.apiTypes=["UIEvents","MouseEvents","Events"];(function(a,b){for(var e=E.events.apiTypes,d=a.Natives.split(/\s+/g),o=0,l=d.length;o-1&&this.media.addEventListener(a,function(C){if(e.data.events[a])for(l=e.data.events[a].slice();l.length;)l.shift().call(e,C)},false);return this},unlisten:function(a,b){var e,d=this.data.events[a];if(d){if(typeof b=== +"string"){for(e=0;ea.media.currentTime&&b.start<=a.media.currentTime&&!b._running){b._running=true;a.data.running[b._natives.type].push(b);if(!a.data.disabled[b._natives.type]){b._natives.start.call(a,null,b);a.emit("trackstart",c.extend({},b,{plugin:b._natives.type,type:"trackstart",track:b}))}}};s.end=function(a,b){var e;if((b.end<=a.media.currentTime||b.start> +a.media.currentTime)&&b._running){e=a.data.running[b._natives.type];b._running=false;e.splice(e.indexOf(b),1);if(!a.data.disabled[b._natives.type]){b._natives.end.call(a,null,b);a.emit("trackend",c.extend({},b,{plugin:b._natives.type,type:"trackend",track:b}))}}};m.prototype.where=function(a){return(this.parent.getTrackEvents()||[]).filter(function(b){var e,d;if(!a)return true;for(e in a){d=a[e];if(b[e]&&b[e]===d||b._natives[e]&&b._natives[e]===d)return true}return false})};m.prototype.add=function(a){var b= +this.byStart,e=this.byEnd,d;a&&a._id&&this.parent.data.history.push(a._id);a.start=c.util.toSeconds(a.start,this.parent.options.framerate);a.end=c.util.toSeconds(a.end,this.parent.options.framerate);for(d=b.length-1;d>=0;d--)if(a.start>=b[d].start){b.splice(d+1,0,a);break}for(b=e.length-1;b>=0;b--)if(a.end>e[b].end){e.splice(b+1,0,a);break}d<=this.parent.data.trackEvents.startIndex&&a.start<=this.parent.data.trackEvents.previousUpdateTime&&this.parent.data.trackEvents.startIndex++;b<=this.parent.data.trackEvents.endIndex&& +a.end-1;){b=this.byStart[o];e=this.byEnd[o];if(!b._id){q.push(b);C.push(e)}if(b._id){b._id!==a&&q.push(b);e._id!==a&&C.push(e);if(b._id===a)l=o}o++}d=this.animating.length; +o=0;if(d)for(;--d>-1;){b=this.animating[o];b._id||k.push(b);b._id&&b._id!==a&&k.push(b);o++}l<=this.startIndex&&this.startIndex--;l<=this.endIndex&&this.endIndex--;this.byStart=q;this.byEnd=C;this.animating=k;this.count--;d=this.parent.data.history.length;for(o=0;oe&&r._running===false){r._running=true;a.data.running[f].push(r);if(!a.data.disabled[f]){d.start.call(a,b, +r);a.emit("trackstart",c.extend({},r,{plugin:f,type:"trackstart",track:r}))}}q++}else{c.removeTrackEvent(a,r._id);return}}}else if(d>e){for(;o.byStart[q]&&o.byStart[q].start>e;){r=o.byStart[q];f=(d=r._natives)&&d.type;if(!d||N[f]||a[f]){if(r._running===true){r._running=false;x=a.data.running[f];x.splice(x.indexOf(r),1);if(!a.data.disabled[f]){d.end.call(a,b,r);a.emit("trackend",c.extend({},r,{plugin:f,type:"trackend",track:r}))}}q--}else{c.removeTrackEvent(a,r._id);return}}for(;o.byEnd[l]&&o.byEnd[l].end> +e;){r=o.byEnd[l];f=(d=r._natives)&&d.type;if(!d||N[f]||a[f]){if(r.start<=e&&r._running===false){r._running=true;a.data.running[f].push(r);if(!a.data.disabled[f]){d.start.call(a,b,r);a.emit("trackstart",c.extend({},r,{plugin:f,type:"trackstart",track:r}))}}l--}else{c.removeTrackEvent(a,r._id);return}}}o.endIndex=l;o.startIndex=q;o.previousUpdateTime=e;o.byStart.length= +0)c.error("'"+a+"' is a protected function name");else{var d=typeof b==="function",o=["start","end","type","manifest"],l=["_setup","_teardown","start","end","frame"],q={},C=function(r,f){r=r||c.nop;f=f||c.nop;return function(){r.apply(this,arguments);f.apply(this,arguments)}};c.manifest[a]=e=e||b.manifest||{};l.forEach(function(r){b[r]=g(b[r]||c.nop,a)});var k=function(r,f){if(!f)return this;if(f.ranges&&c.isArray(f.ranges)){c.forEach(f.ranges,function(M){M=c.extend({},f,M);delete M.ranges;this[a](M)}, +this);return this}var x=f._natives={},R="",J;c.extend(x,r);f._natives.type=f._natives.plugin=a;f._running=false;x.start=x.start||x["in"];x.end=x.end||x.out;if(f.once)x.end=C(x.end,function(){this.removeTrackEvent(f._id)});x._teardown=C(function(){var M=v.call(arguments),z=this.data.running[x.type];M.unshift(null);M[1]._running&&z.splice(z.indexOf(f),1)&&x.end.apply(this,M);M[1]._running=false;this.emit("trackend",c.extend({},f,{plugin:x.type,type:"trackend",track:c.getTrackEvent(this,f.id||f._id)}))}, +x._teardown);x._teardown=C(x._teardown,function(){this.emit("trackteardown",c.extend({},f,{plugin:a,type:"trackteardown",track:c.getTrackEvent(this,f.id||f._id)}))});f.compose=f.compose||[];if(typeof f.compose==="string")f.compose=f.compose.split(" ");f.effect=f.effect||[];if(typeof f.effect==="string")f.effect=f.effect.split(" ");f.compose=f.compose.concat(f.effect);f.compose.forEach(function(M){R=c.compositions[M]||{};l.forEach(function(z){x[z]=C(x[z],R[z])})});f._natives.manifest=e;if(!("start"in +f))f.start=f["in"]||0;if(!f.end&&f.end!==0)f.end=f.out||Number.MAX_VALUE;if(!A.call(f,"toString"))f.toString=function(){var M=["start: "+f.start,"end: "+f.end,"id: "+(f.id||f._id)];f.target!=null&&M.push("target: "+f.target);return a+" ( "+M.join(", ")+" )"};if(!f.target){J="options"in e&&e.options;f.target=J&&"target"in J&&J.target}if(!f._id&&f._natives)f._id=c.guid(f._natives.type);if(f instanceof s){if(f._natives){f._id=f.id||f._id||c.guid(f._natives.type);if(f._natives._setup){f._natives._setup.call(this, +f);this.emit("tracksetup",c.extend({},f,{plugin:f._natives.type,type:"tracksetup",track:f}))}}this.data.trackEvents.add(f);s.start(this,f);this.timeUpdate(this,null,true);f._id&&c.addTrackEvent.ref(this,f)}else c.addTrackEvent(this,f);c.forEach(r,function(M,z){o.indexOf(z)===-1&&this.on(z,M)},this);return this};c.p[a]=q[a]=function(r,f){var x,R;if(r&&!f)f=r;else if(x=this.getTrackEvent(r)){R=f;var J={},M;for(M in x)if(A.call(R,M)&&A.call(x,M))J[M]=x[M];if(x._natives._update){this.data.trackEvents.remove(x); +if(A.call(f,"start"))x.start=f.start;if(A.call(f,"end"))x.end=f.end;s.end(this,x);d&&b.call(this,x);x._natives._update.call(this,x,f);this.data.trackEvents.add(x);s.start(this,x)}else{c.extend(x,f);this.data.trackEvents.remove(r);x._natives._teardown&&x._natives._teardown.call(this,x);c.removeTrackEvent.ref(this,r);if(d)k.call(this,b.call(this,x),x);else{x._id=x.id||x._id||c.guid(x._natives.type);if(x._natives&&x._natives._setup){x._natives._setup.call(this,x);this.emit("tracksetup",c.extend({},x, +{plugin:x._natives.type,type:"tracksetup",track:x}))}this.data.trackEvents.add(x);s.start(this,x);this.timeUpdate(this,null,true);c.addTrackEvent.ref(this,x)}this.emit("trackchange",{id:x.id,type:"trackchange",previousValue:J,currentValue:x,track:x});return this}x._natives.type!=="cue"&&this.emit("trackchange",{id:x.id,type:"trackchange",previousValue:J,currentValue:R,track:x});return this}else f.id=r;this.data.running[a]=this.data.running[a]||[];x=c.extend({},this.options.defaults&&this.options.defaults[a]|| +{},f);k.call(this,d?b.call(this,x):b,x);return this};e&&c.extend(b,{manifest:e});var N={fn:q[a],definition:b,base:b,parents:[],name:a};c.registry.push(c.extend(q,N,{type:a}));c.registryByName[a]=N;return q}};c.plugin.errors=[];c.plugin.debug=c.version==="d05323f";c.removePlugin=function(a,b){if(!b){b=a;a=c.p;if(c.protect.natives.indexOf(b.toLowerCase())>=0){c.error("'"+b+"' is a protected function name");return}var e=c.registry.length,d;for(d=0;d-1){o=o.split(";");l=0;if(b&&typeof b==="number")l=parseFloat(o[1],10)/b;e[d]=parseInt(o[0],10)+l}d=e[0];return{1:parseFloat(d,10),2:parseInt(d,10)*60+parseFloat(e[1],10),3:parseInt(d,10)*3600+parseInt(e[1],10)*60+parseFloat(e[2],10)}[e.length||1]}};c.p.cue=c.p.exec;c.protect={natives:function(a){return Object.keys?Object.keys(a):function(b){var e,d=[];for(e in b)A.call(b,e)&&d.push(e);return d}(a)}(c.p).map(function(a){return a.toLowerCase()})}; +c.forEach({listen:"on",unlisten:"off",trigger:"emit",exec:"cue"},function(a,b){var e=c.p[b];c.p[b]=function(){if(typeof console!=="undefined"&&console.warn){console.warn("Deprecated method '"+b+"', "+(a==null?"do not use.":"use '"+a+"' instead."));c.p[b]=e}return c.p[a].apply(this,[].slice.call(arguments))}});j.Popcorn=c}else{j.Popcorn={isSupported:false};for(p="byId forEach extend effects error guid sizeOf isArray nop position disable enable destroyaddTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId timeUpdate plugin removePlugin compose effect xhr getJSONP getScript".split(/\s+/);p.length;)j.Popcorn[p.shift()]= +function(){}}})(window,window.document);(function(j,t){function s(p){p=typeof p==="string"?p:[p.language,p.region].join("-");var i=p.split("-");return{iso6391:p,language:i[0]||"",region:i[1]||""}}var m=j.navigator,g=s(m.userLanguage||m.language);t.locale={get:function(){return g},set:function(p){g=s(p);t.locale.broadcast();return g},broadcast:function(p){var i=t.instances,h=i.length,v=0,A;for(p=p||"locale:changed";v=0)j.error("'"+s+"' is a protected function name");else{if(typeof m==="function"&&!g){g=m;m=""}if(!(typeof g!=="function"||typeof m!=="string")){var p={};p[s]=function(i,h,v){if(!i)return this;if(typeof h!=="function"&&!v){v=h;h=null}var A=this;j.xhr({url:i,dataType:m,success:function(n){var u,B,w=0;n=g(n,v).data||[];if(u=n.length){for(;w=4){A=new Date/1E3;H.dispatchEvent("play");I()}};H.pause=function(){this.paused=true;H.dispatchEvent("pause")};j.player.defineProperty(H,"currentTime",{get:function(){return n}, +set:function(y){n=+y;H.dispatchEvent("timeupdate");return n},configurable:true});j.player.defineProperty(H,"volume",{get:function(){return B},set:function(y){B=+y;H.dispatchEvent("volumechange");return B},configurable:true});j.player.defineProperty(H,"muted",{get:function(){return w},set:function(y){w=+y;H.dispatchEvent("volumechange");return w},configurable:true});j.player.defineProperty(H,"readyState",{get:function(){return u},set:function(y){return u=y},configurable:true});H.addEventListener=function(y, +P){E[y]||(E[y]=[]);E[y].push(P);return P};H.removeEventListener=function(y,P){var a,b=E[y];if(b){for(a=E[y].length-1;a>=0;a--)P===b[a]&&b.splice(a,1);return P}};H.dispatchEvent=function(y){var P,a=y.type;if(!a){a=y;if(y=j.events.getInterface(a)){P=document.createEvent(y);P.initEvent(a,true,true,window,1)}}if(E[a])for(y=E[a].length-1;y>=0;y--)E[a][y].call(this,P,this)};H.src=i||"";H.duration=0;H.paused=true;H.ended=0;h&&h.events&&j.forEach(h.events,function(y,P){H.addEventListener(P,y,false)});if(m._canPlayType(L.nodeName, +i)!==false)if(m._setup)m._setup.call(H,h);else{H.readyState=4;H.dispatchEvent("loadedmetadata");H.dispatchEvent("loadeddata");H.dispatchEvent("canplaythrough")}else setTimeout(function(){H.dispatchEvent("error")},0);p=new j.p.init(H,h);if(m._teardown)p.destroy=t(p.destroy,function(){m._teardown.call(H,h)});return p};g.canPlayType=m._canPlayType=m._canPlayType||j.nop;j[s]=j.player.registry[s]=g}};j.player.registry={};j.player.defineProperty=Object.defineProperty||function(s,m,g){s.__defineGetter__(m, +g.get||j.nop);s.__defineSetter__(m,g.set||j.nop)};j.player.playerQueue=function(){var s=[],m=false;return{next:function(){m=false;s.shift();s[0]&&s[0]()},add:function(g){s.push(function(){m=true;g&&g()});!m&&s[0]()}}};j.smart=function(s,m,g){var p=typeof s==="string"?j.dom.find(s):s,i,h,v,A,n,u="HTMLYouTubeVideoElement HTMLVimeoVideoElement HTMLSoundCloudAudioElement HTMLNullVideoElement".split(" ");if(p){m=typeof m==="string"?[m]:m;s=0;for(n=m.length;s';s=0;for(n=m.length;s';u+="";h.innerHTML=u;g&&g.events&&g.events.error&&p.addEventListener("error",g.events.error,false);return j("#"+B,g)}else j.error("Specified target `"+s+"` was not found.")}})(Popcorn);(function(j){document.addEventListener("DOMContentLoaded",function(){var t=document.querySelectorAll("[data-timeline-sources]");j.forEach(t,function(s,m){var g=t[m],p,i,h;if(!g.id)g.id=j.guid("__popcorn");if(g.nodeType&&g.nodeType===1){h=j("#"+g.id);p=(g.getAttribute("data-timeline-sources")||"").split(",");p[0]&&j.forEach(p,function(v){i=v.split("!");if(i.length===1){i=v.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/)[2].split(".");i[0]="parse"+i[1].toUpperCase();i[1]=v}p[0]&&h[i[0]]&&h[i[0]](i[1])});h.autoplay()&& +h.play()}})},false)})(Popcorn);(function(j,t){function s(g){var p=s.options;g=p.parser[p.strictMode?"strict":"loose"].exec(g);for(var i={},h=14;h--;)i[p.key[h]]=g[h]||"";i[p.q.name]={};i[p.key[12]].replace(p.q.parser,function(v,A,n){if(A)i[p.q.name][A]=n});return i}s.options={strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, +loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};var m={length:0,start:j.nop,end:j.nop};window.MediaError=window.MediaError||function(){function g(p,i){this.code=p||null;this.message=i||""}g.MEDIA_ERR_NONE_ACTIVE=0;g.MEDIA_ERR_ABORTED=1;g.MEDIA_ERR_NETWORK=2;g.MEDIA_ERR_DECODE=3;g.MEDIA_ERR_NONE_SUPPORTED=4;return g}();j._MediaElementProto=function(){var g= +{},p;Object.prototype.__defineGetter__||(g=t.createElement("div"));g._util={type:"HTML5",TIMEUPDATE_MS:250,MIN_WIDTH:300,MIN_HEIGHT:150,isAttributeSet:function(i){return typeof i==="string"||i===true},parseUri:s};g.addEventListener=function(i,h,v){t.addEventListener(this._eventNamespace+i,h,v)};g.removeEventListener=function(i,h,v){t.removeEventListener(this._eventNamespace+i,h,v)};g.dispatchEvent=function(i){var h=t.createEvent("CustomEvent");h.initCustomEvent(this._eventNamespace+i,false,false, +{type:i,target:this.parentNode,data:null});t.dispatchEvent(h)};g.load=j.nop;g.canPlayType=function(){return""};g.getBoundingClientRect=function(){return p.getBoundingClientRect()};g.NETWORK_EMPTY=0;g.NETWORK_IDLE=1;g.NETWORK_LOADING=2;g.NETWORK_NO_SOURCE=3;g.HAVE_NOTHING=0;g.HAVE_METADATA=1;g.HAVE_CURRENT_DATA=2;g.HAVE_FUTURE_DATA=3;g.HAVE_ENOUGH_DATA=4;Object.defineProperties(g,{currentSrc:{get:function(){return this.src!==undefined?this.src:""},configurable:true},parentNode:{get:function(){return p}, +set:function(i){p=i},configurable:true},preload:{get:function(){return"auto"},set:j.nop,configurable:true},controls:{get:function(){return true},set:j.nop,configurable:true},poster:{get:function(){return""},set:j.nop,configurable:true},crossorigin:{get:function(){return""},configurable:true},played:{get:function(){return m},configurable:true},seekable:{get:function(){return m},configurable:true},buffered:{get:function(){return m},configurable:true},defaultMuted:{get:function(){return false},configurable:true}, +defaultPlaybackRate:{get:function(){return 1},configurable:true},style:{get:function(){return this.parentNode.style},configurable:true},id:{get:function(){return this.parentNode.id},configurable:true}});return g}})(Popcorn,window.document);(function(j,t){function s(){return"maybe"}function m(g,p){var i=typeof g==="string"?t.querySelector(g):g,h=t.createElement(p);i.appendChild(h);h._canPlaySrc=s;return h}j.HTMLVideoElement=function(g){return m(g,"video")};j.HTMLVideoElement._canPlaySrc=s;j.HTMLAudioElement=function(g){return m(g,"audio")};j.HTMLAudioElement._canPlaySrc=s})(Popcorn,window.document);(function(j,t,s){function m(){if(t.jwplayer){v=true;for(var u=n.length;u--;){n[u]();delete n[u]}}else setTimeout(m,100)}function g(){if(!A){if(!t.jwplayer){var u=s.createElement("script");u.src="https://jwpsrv.com/library/zaIF4JI9EeK2FSIACpYGxA.js";var B=s.getElementsByTagName("script")[0];B.parentNode.insertBefore(u,B)}A=true;m()}return v}function p(u){n.unshift(u)}function i(u){function B(K){z.unshift(K)}function w(){var K=J.getDuration();if(K==-1||K==undefined)setTimeout(w,0);else{k.duration=K; +q.dispatchEvent("durationchange");N=true;k.readyState=q.HAVE_METADATA;q.dispatchEvent("loadedmetadata");q.dispatchEvent("loadeddata");k.readyState=q.HAVE_FUTURE_DATA;q.dispatchEvent("canplay");for(x=true;z.length;){z[0]();z.shift()}k.readyState=q.HAVE_ENOUGH_DATA;q.dispatchEvent("canplaythrough")}}function E(){if(r)r=false;else if(F){F=false;w()}else e()}function L(){if(k.seeking){k.ended=false;k.seeking=false;q.dispatchEvent("timeupdate");q.dispatchEvent("seeked");q.dispatchEvent("canplay");q.dispatchEvent("canplaythrough")}} +function H(){J.onPause(E);J.onTime(function(){if(!k.ended&&!k.seeking){k.currentTime=J.getPosition();q.dispatchEvent("timeupdate")}});J.onSeek(L);J.onPlay(function(){if(!k.ended)if(S){S=false;if(k.autoplay||!k.paused){k.paused=false;B(a);w()}else{f=F=true;J.pause(true)}}else if(f){f=false;r=true;J.pause(true)}else a()});J.onBufferChange(b);J.onComplete(d);J.play(true)}function c(K){var T={name:"MediaError"};T.message=K.message;T.code=K.code||5;k.error=T;q.dispatchEvent("error")}function I(K){if(q._canPlaySrc(K)){var T= +q._util.parseUri(K).queryKey;k.controls=T.controls=T.controls||k.controls;k.src=K;if(g()){if(N)N&&J&&J.destroy();T={width:"100%",height:"100%",autostart:k.autoplay,controls:k.controls};if(typeof K=="string")T.file=K;else T.sources=K;jwplayer(C.id).setup(T);J=jwplayer(C.id);J.onReady(H);J.onError(c);jwplayer.utils.log=function(D,V){if(typeof console!=="undefined"&&typeof console.log!=="undefined")V?console.log(D,V):console.log(D);D==="No suitable players found and fallback enabled"&&c({message:D,code:4})}; +k.networkState=q.NETWORK_LOADING;q.dispatchEvent("loadstart");q.dispatchEvent("progress")}else p(function(){I(K)})}else{k.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};q.dispatchEvent("error")}}function y(K){k.currentTime=K;if(x){P();J.seek(K)}else B(function(){P();J.seek(K)})}function P(){k.seeking=true;if(k.paused)f=true;q.dispatchEvent("seeking")}function a(){k.paused=false;if(N&&M){M=false;if(k.loop&&!R||!k.loop){R=true;q.dispatchEvent("play")}q.dispatchEvent("playing")}} +function b(){q.dispatchEvent("progress")}function e(){k.paused=true;if(!M){M=true;q.dispatchEvent("pause")}}function d(){if(k.loop)y(0);else{k.ended=true;e();q.dispatchEvent("timeupdate");q.dispatchEvent("ended")}}function o(K){k.volume=K;if(x){J.setVolume(k.volume*100);q.dispatchEvent("volumechange")}else B(function(){o(k.volume)})}function l(K){k.muted=K;if(x){J.setMute(K);q.dispatchEvent("volumechange")}else B(function(){l(k.muted)})}if(!t.postMessage)throw"ERROR: HTMLJWPlayerVideoElement requires window.postMessage"; +var q=new j._MediaElementProto,C=typeof u==="string"?s.querySelector(u):u,k={src:h,networkState:q.NETWORK_EMPTY,readyState:q.HAVE_NOTHING,seeking:false,autoplay:h,preload:h,controls:false,loop:false,poster:h,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},N=false,r=false,f=false,x=false,R=false,J,M=true,z=[],S=true,F=false;q._eventNamespace=j.guid("HTMLJWPlayerVideoElement::");q.parentNode=C;q._util.type="JWPlayer";q.play=function(){q.dispatchEvent("play");k.paused= +false;if(x){if(k.ended){y(0);k.ended=false}J.play(true)}else B(function(){q.play()})};q.pause=function(){k.paused=true;x?J.pause(true):B(function(){q.pause()})};Object.defineProperties(q,{src:{get:function(){return k.src},set:function(K){K&&K!==k.src&&I(K)}},autoplay:{get:function(){return k.autoplay},set:function(K){k.autoplay=q._util.isAttributeSet(K)}},loop:{get:function(){return k.loop},set:function(K){k.loop=q._util.isAttributeSet(K)}},width:{get:function(){return q.parentNode.offsetWidth}}, +height:{get:function(){return q.parentNode.offsetHeight}},currentTime:{get:function(){return k.currentTime},set:function(K){y(K)}},duration:{get:function(){return J.getDuration()}},ended:{get:function(){return k.ended}},paused:{get:function(){return k.paused}},seeking:{get:function(){return k.seeking}},readyState:{get:function(){return k.readyState}},networkState:{get:function(){return k.networkState}},volume:{get:function(){return k.volume},set:function(K){if(K<0||K>1)throw"Volume value must be between 0.0 and 1.0"; +o(K)}},muted:{get:function(){return k.muted},set:function(K){l(q._util.isAttributeSet(K))}},error:{get:function(){return k.error}},buffered:{get:function(){return{start:function(K){if(K===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(K){if(K===0){K=J.getDuration();if(!K)return 0;return K*(J.getBuffer()/100)}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}}}});q._canPlaySrc=j.HTMLJWPlayerVideoElement._canPlaySrc;q.canPlayType=j.HTMLJWPlayerVideoElement.canPlayType;return q}var h= +"",v=false,A=false,n=[];j.HTMLJWPlayerVideoElement=function(u){return new i(u)};j.HTMLJWPlayerVideoElement._canPlaySrc=function(u){if(typeof u=="string"){if(/.+\.+/g.exec(u))return"probably"}else return"probably"};j.HTMLJWPlayerVideoElement.canPlayType=function(){return"probably"}})(Popcorn,window,document);(function(j,t){function s(i){this.startTime=0;this.currentTime=i.currentTime||0;this.duration=i.duration||NaN;this.playInterval=null;this.paused=true;this.playbackRate=this.defaultPlaybackRate=1;this.ended=i.endedCallback||j.nop}function m(i){function h(a){y.push(a)}function v(){if(!H)return 0;return c.currentTime}function A(a){if(a!==v())if(H){I.seeking=true;w.dispatchEvent("seeking");c.seekTo(a);I.ended=false;I.seeking=false;w.dispatchEvent("timeupdate");w.dispatchEvent("seeked");w.dispatchEvent("canplay"); +w.dispatchEvent("canplaythrough")}else h(function(){A(a)})}function n(){w.dispatchEvent("timeupdate")}function u(){I.paused=true;clearInterval(P);w.dispatchEvent("pause")}function B(){if(I.loop){A(0);w.play()}else{I.ended=true;u();w.dispatchEvent("timeupdate");w.dispatchEvent("ended")}}var w=new j._MediaElementProto,E=typeof i==="string"?t.querySelector(i):i,L=t.createElement("div"),H=false,c,I={src:g,networkState:w.NETWORK_EMPTY,readyState:w.HAVE_NOTHING,autoplay:g,preload:g,controls:g,loop:false, +poster:g,volume:1,muted:false,width:E.width|0?E.width:w._util.MIN_WIDTH,height:E.height|0?E.height:w._util.MIN_HEIGHT,seeking:false,ended:false,paused:1,error:null},y=[],P;w._eventNamespace=j.guid("HTMLNullVideoElement::");w.parentNode=E;w._util.type="NullVideo";w.play=function(){if(H){c.play();if(I.paused){if(I.paused===1){I.paused=false;w.dispatchEvent("play");w.dispatchEvent("playing")}else{if(I.ended){A(0);I.ended=false}if(I.paused){I.paused=false;I.loop||w.dispatchEvent("play");w.dispatchEvent("playing")}}P= +setInterval(n,w._util.TIMEUPDATE_MS)}}else h(function(){w.play()})};w.pause=function(){if(H){c.pause();I.paused||u()}else h(function(){w.pause()})};Object.defineProperties(w,{src:{get:function(){return I.src},set:function(a){if(a&&a!==I.src)if(w._canPlaySrc(a)){I.src=a;if(H)if(H&&c){c.pause();c=null;E.removeChild(L);L=t.createElement("div")}L.width=I.width;L.height=I.height;E.appendChild(L);a=p.exec(a);c=new s({currentTime:+a[1],duration:+a[2],endedCallback:B});w.dispatchEvent("loadstart");w.dispatchEvent("progress"); +w.dispatchEvent("durationchange");H=true;I.networkState=w.NETWORK_IDLE;I.readyState=w.HAVE_METADATA;w.dispatchEvent("loadedmetadata");w.dispatchEvent("loadeddata");I.readyState=w.HAVE_FUTURE_DATA;w.dispatchEvent("canplay");I.readyState=w.HAVE_ENOUGH_DATA;for(w.dispatchEvent("canplaythrough");y.length;){a=y.shift();a()}I.autoplay&&w.play()}else{I.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};w.dispatchEvent("error")}}},autoplay:{get:function(){return I.autoplay}, +set:function(a){I.autoplay=w._util.isAttributeSet(a)}},loop:{get:function(){return I.loop},set:function(a){I.loop=w._util.isAttributeSet(a)}},width:{get:function(){return L.width},set:function(a){L.width=a;I.width=L.width}},height:{get:function(){return L.height},set:function(a){L.height=a;I.height=L.height}},currentTime:{get:function(){return v()},set:function(a){A(a)}},duration:{get:function(){return c?c.duration:NaN}},ended:{get:function(){return I.ended}},paused:{get:function(){return I.paused}}, +seeking:{get:function(){return I.seeking}},readyState:{get:function(){return I.readyState}},networkState:{get:function(){return I.networkState}},volume:{get:function(){return I.volume},set:function(a){if(a<0||a>1)throw"Volume value must be between 0.0 and 1.0";I.volume=a;w.dispatchEvent("volumechange")}},muted:{get:function(){return I.muted},set:function(a){a=w._util.isAttributeSet(a);I.muted=a;w.dispatchEvent("volumechange")}},playbackRate:{get:function(){return c.playbackRate},set:function(a){c.playbackRate= +a;w.dispatchEvent("ratechange")}},error:{get:function(){return I.error}}});w._canPlaySrc=j.HTMLNullVideoElement._canPlaySrc;w.canPlayType=j.HTMLNullVideoElement.canPlayType;return w}var g="",p=/#t=(\d+\.?\d*)?,?(\d+\.?\d*)/;s.prototype={play:function(){var i=this;if(this.paused){this.paused=false;this.startTime=Date.now();this.playInterval=setInterval(function(){i.currentTime+=(Date.now()-i.startTime)/(1E3/i.playbackRate);i.startTime=Date.now();if(i.currentTime>=i.duration){i.pause(i.duration);i.ended()}i.currentTime< +0&&i.pause(0)},16)}},pause:function(){if(!this.paused){this.paused=true;clearInterval(this.playInterval)}},seekTo:function(i){i=i<0?0:i;this.currentTime=i=i>this.duration?this.duration:i}};j.HTMLNullVideoElement=function(i){return new m(i)};j.HTMLNullVideoElement._canPlaySrc=function(i){return p.test(i)?"probably":g};j.HTMLNullVideoElement.canPlayType=function(i){return i==="video/x-nullvideo"?"probably":g}})(Popcorn,document);(function(j,t,s){function m(){if(!A){j.getScript("https://w.soundcloud.com/player/api.js",function(){j.getScript("https://connect.soundcloud.com/sdk.js",function(){v=true;SC.initialize({client_id:"PRaNFlda6Bhf5utPjUsptg"});for(var u=n.length;u--;){n[u]();delete n[u]}})});A=true}return v}function g(u){n.unshift(u)}function p(u){function B(z){x.unshift(z)}function w(){f.bind(SC.Widget.Events.LOAD_PROGRESS,function(z){P({type:"loadProgress",data:z.currentPosition/1E3})});f.bind(SC.Widget.Events.PLAY_PROGRESS, +function(z){P({type:"playProgress",data:z.currentPosition/1E3})});f.bind(SC.Widget.Events.PLAY,function(){P({type:"play"})});f.bind(SC.Widget.Events.PAUSE,function(){P({type:"pause"})});f.bind(SC.Widget.Events.SEEK,function(){f.getPosition(function(z){z=z/1E3;if(k.seeking)if(Math.floor(z)!==Math.floor(k.currentTime))f.seekTo(k.currentTime*1E3);else{k.ended=false;k.seeking=false;l.dispatchEvent("timeupdate");l.dispatchEvent("seeked");l.dispatchEvent("canplay");l.dispatchEvent("canplaythrough")}else P({type:"seek", +data:z})})});f.bind(SC.Widget.Events.FINISH,function(){P({type:"finish"})});N=true;f.getDuration(L)}function E(){f.bind(SC.Widget.Events.PLAY_PROGRESS,function(z){f.setVolume(0);if(z.currentPosition>0){f.unbind(SC.Widget.Events.PLAY_PROGRESS);f.bind(SC.Widget.Events.PAUSE,function(){f.unbind(SC.Widget.Events.PAUSE);f.setVolume(1);f.bind(SC.Widget.Events.SEEK,function(){f.unbind(SC.Widget.Events.SEEK);w()});f.seekTo(0)});f.pause()}});f.play()}function L(z){z/=1E3;var S=k.duration;if(S!==z){k.duration= +z;l.dispatchEvent("durationchange");if(isNaN(S)){k.networkState=l.NETWORK_IDLE;k.readyState=l.HAVE_METADATA;l.dispatchEvent("loadedmetadata");l.dispatchEvent("loadeddata");k.readyState=l.HAVE_FUTURE_DATA;l.dispatchEvent("canplay");k.readyState=l.HAVE_ENOUGH_DATA;l.dispatchEvent("canplaythrough");for(z=x.length;z--;){x[z]();delete x[z]}k.paused&&k.autoplay&&l.play()}}}function H(z){function S(){k.seeking=true;l.dispatchEvent("seeking");f.seekTo(z)}k.currentTime=z;z*=1E3;N?S():addMediaReadyCallback(S)} +function c(){k.paused=true;if(!r){r=true;clearInterval(R);l.dispatchEvent("pause")}}function I(){l.dispatchEvent("timeupdate")}function y(z){k.currentTime=z;z!==M&&l.dispatchEvent("timeupdate");M=z}function P(z){switch(z.type){case "loadProgress":l.dispatchEvent("progress");break;case "playProgress":y(z.data);break;case "play":if(!J){J=setInterval(a,i);k.loop&&l.dispatchEvent("play")}R=setInterval(I,l._util.TIMEUPDATE_MS);k.paused=false;if(r){r=false;k.loop||l.dispatchEvent("play");l.dispatchEvent("playing")}break; +case "pause":c();break;case "finish":if(k.loop){H(0);l.play()}else{k.ended=true;l.pause();c();l.dispatchEvent("timeupdate");l.dispatchEvent("ended")}break;case "seek":y(z.data)}}function a(){k.ended||f.getPosition(function(z){y(z/1E3)})}function b(z){if(l._canPlaySrc(z)){k.src=z;if(N)if(N&&f){clearInterval(J);f.pause();f.unbind(SC.Widget.Events.READY);f.unbind(SC.Widget.Events.LOAD_PROGRESS);f.unbind(SC.Widget.Events.PLAY_PROGRESS);f.unbind(SC.Widget.Events.PLAY);f.unbind(SC.Widget.Events.PAUSE); +f.unbind(SC.Widget.Events.SEEK);f.unbind(SC.Widget.Events.FINISH);q.removeChild(C);C=s.createElement("iframe")}if(m()){N=false;SC.get("/resolve",{url:z},function(S){var F;if(S.errors){F={name:"MediaError"};if(S.errors[0])if(S.errors[0].error_message==="404 - Not Found"){F.message="Video not found.";F.code=MediaError.MEDIA_ERR_NETWORK}k.error=F;l.dispatchEvent("error")}C.id=j.guid("soundcloud-");C.width=k.width;C.height=k.height;C.frameBorder=0;C.webkitAllowFullScreen=true;C.mozAllowFullScreen=true; +C.allowFullScreen=true;o(k.controls);q.appendChild(C);C.onload=function(){C.onload=null;f=SC.Widget(C);f.bind(SC.Widget.Events.READY,E);k.networkState=l.NETWORK_LOADING;l.dispatchEvent("loadstart");l.dispatchEvent("progress")};C.src="https://w.soundcloud.com/player/?url="+S.uri+"&show_artwork=false&buying=false&liking=false&sharing=false&download=false&show_comments=false&show_user=false&single_active=false"})}else g(function(){b(z)})}else{k.error={name:"MediaError",message:"Media Source Not Supported", +code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};l.dispatchEvent("error")}}function e(z){k.volume=z;if(N){f.setVolume(z);l.dispatchEvent("volumechange")}else B(function(){e(z)})}function d(z){if(N)if(z){k.muted=k.volume;e(0)}else{k.muted=0;e(k.muted)}else{k.muted=z?1:0;B(function(){d(z)})}}function o(z){if(N){C.style.position="absolute";C.style.visibility=z?"visible":"hidden"}else{C.style.opacity=z?"1":"0";C.style.pointerEvents=z?"auto":"none"}k.controls=z}if(!t.postMessage)throw"ERROR: HTMLSoundCloudAudioElement requires window.postMessage"; +var l=new j._MediaElementProto,q=typeof u==="string"?j.dom.find(u):u,C=s.createElement("iframe"),k={src:h,networkState:l.NETWORK_EMPTY,readyState:l.HAVE_NOTHING,seeking:false,autoplay:h,preload:h,controls:false,loop:false,poster:h,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,width:q.width|0?q.width:l._util.MIN_WIDTH,height:q.height|0?q.height:l._util.MIN_HEIGHT,error:null},N=false,r=true,f,x=[],R,J,M=0;l._eventNamespace=j.guid("HTMLSoundCloudAudioElement::");l.parentNode=q; +l._util.type="SoundCloud";l.play=function(){k.paused=false;if(N){k.ended&&H(0);f.play()}else B(function(){l.play()})};l.pause=function(){k.paused=true;N?f.pause():B(function(){l.pause()})};Object.defineProperties(l,{src:{get:function(){return k.src},set:function(z){z&&z!==k.src&&b(z)}},autoplay:{get:function(){return k.autoplay},set:function(z){k.autoplay=l._util.isAttributeSet(z)}},loop:{get:function(){return k.loop},set:function(z){k.loop=l._util.isAttributeSet(z)}},width:{get:function(){return C.width}, +set:function(z){C.width=z;k.width=C.width}},height:{get:function(){return C.height},set:function(z){C.height=z;k.height=C.height}},currentTime:{get:function(){return k.currentTime},set:function(z){H(z)}},duration:{get:function(){return k.duration}},ended:{get:function(){return k.ended}},paused:{get:function(){return k.paused}},seeking:{get:function(){return k.seeking}},readyState:{get:function(){return k.readyState}},networkState:{get:function(){return k.networkState}},volume:{get:function(){return k.muted> +0?k.muted:k.volume},set:function(z){if(z<0||z>1)throw"Volume value must be between 0.0 and 1.0";e(z)}},muted:{get:function(){return k.muted>0},set:function(z){d(l._util.isAttributeSet(z))}},error:{get:function(){return k.error}},controls:{get:function(){return k.controls},set:function(z){o(!!z)}}});l._canPlaySrc=j.HTMLSoundCloudAudioElement._canPlaySrc;l.canPlayType=j.HTMLSoundCloudAudioElement.canPlayType;return l}var i=16,h="",v=false,A=false,n=[];j.HTMLSoundCloudAudioElement=function(u){return new p(u)}; +j.HTMLSoundCloudAudioElement._canPlaySrc=function(u){return/(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/.test(u)?"probably":h};j.HTMLSoundCloudAudioElement.canPlayType=function(u){return u==="audio/x-soundcloud"?"probably":h}})(Popcorn,window,document);(function(j,t,s){function m(v){var A=this,n=v.src.split("?")[0];if(n.substr(0,2)==="//")n=t.location.protocol+n;"play pause paused seekTo unload getCurrentTime getDuration getVideoEmbedCode getVideoHeight getVideoWidth getVideoUrl getColor setColor setLoop getVolume setVolume addEventListener".split(" ").forEach(function(u){A[u]=function(B){B=JSON.stringify({method:u,value:B});v.contentWindow&&v.contentWindow.postMessage(B,n)}})}function g(v){function A(r){q.unshift(r)}function n(r){var f=b.duration; +if(f!==r){b.duration=r;y.dispatchEvent("durationchange");if(isNaN(f)){b.networkState=y.NETWORK_IDLE;b.readyState=y.HAVE_METADATA;y.dispatchEvent("loadedmetadata");y.dispatchEvent("loadeddata");b.readyState=y.HAVE_FUTURE_DATA;y.dispatchEvent("canplay");b.readyState=y.HAVE_ENOUGH_DATA;y.dispatchEvent("canplaythrough");b.autoplay&&y.play();for(r=q.length;r--;){q[r]();delete q[r]}}}}function u(r){if(e){b.seeking=true;y.dispatchEvent("seeking");o.seekTo(r)}else A(function(){u(r)})}function B(){y.dispatchEvent("timeupdate")} +function w(r){(b.currentTime=r)!==N&&y.dispatchEvent("timeupdate");N=b.currentTime}function E(r){if(r.origin===h){var f;try{f=JSON.parse(r.data)}catch(x){console.warn(x)}if(f.player_id==d)switch(f.event){case "ready":o=new m(a);o.addEventListener("loadProgress");o.addEventListener("pause");o.setVolume(0);o.play();break;case "loadProgress":if(parseFloat(f.data.duration)>0&&!e){e=true;o.pause()}break;case "pause":o.setVolume(1);t.removeEventListener("message",E,false);t.addEventListener("message",L, +false);o.addEventListener("loadProgress");o.addEventListener("playProgress");o.addEventListener("play");o.addEventListener("pause");o.addEventListener("finish");o.addEventListener("seek");o.getDuration();b.networkState=y.NETWORK_LOADING;y.dispatchEvent("loadstart");y.dispatchEvent("progress")}}}function L(r){if(r.origin===h){var f;try{f=JSON.parse(r.data)}catch(x){console.warn(x)}if(f.player_id==d){switch(f.method){case "getCurrentTime":w(parseFloat(f.value));break;case "getDuration":n(parseFloat(f.value)); +break;case "getVolume":r=parseFloat(f.value);if(b.volume!==r){b.volume=r;y.dispatchEvent("volumechange")}}switch(f.event){case "loadProgress":y.dispatchEvent("progress");n(parseFloat(f.data.duration));break;case "playProgress":w(parseFloat(f.data.seconds));break;case "play":b.ended&&u(0);if(!k){k=setInterval(H,p);b.loop&&y.dispatchEvent("play")}C=setInterval(B,y._util.TIMEUPDATE_MS);b.paused=false;if(l){l=false;b.loop||y.dispatchEvent("play");y.dispatchEvent("playing")}break;case "pause":b.paused= +true;if(!l){l=true;clearInterval(C);y.dispatchEvent("pause")}break;case "finish":if(b.loop){u(0);y.play()}else{b.ended=true;y.dispatchEvent("ended")}break;case "seek":w(parseFloat(f.data.seconds));b.seeking=false;y.dispatchEvent("timeupdate");y.dispatchEvent("seeked");y.dispatchEvent("canplay");y.dispatchEvent("canplaythrough")}}}}function H(){o.getCurrentTime()}function c(r){b.volume=r;if(e){o.setVolume(r);y.dispatchEvent("volumechange")}else A(function(){c(r)})}function I(r){if(e)if(r){b.muted= +b.volume;c(0)}else{b.muted=0;c(b.muted)}else{b.muted=r?1:0;A(function(){I(r)})}}if(!t.postMessage)throw"ERROR: HTMLVimeoVideoElement requires window.postMessage";var y=new j._MediaElementProto,P=typeof v==="string"?j.dom.find(v):v,a=s.createElement("iframe"),b={src:i,networkState:y.NETWORK_EMPTY,readyState:y.HAVE_NOTHING,seeking:false,autoplay:i,preload:i,controls:false,loop:false,poster:i,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,error:null},e=false,d=j.guid(),o,l=true, +q=[],C,k,N=0;y._eventNamespace=j.guid("HTMLVimeoVideoElement::");y.parentNode=P;y._util.type="Vimeo";y.play=function(){b.paused=false;e?o.play():A(function(){y.play()})};y.pause=function(){b.paused=true;e?o.pause():A(function(){y.pause()})};Object.defineProperties(y,{src:{get:function(){return b.src},set:function(r){if(r&&r!==b.src)if(y._canPlaySrc(r)){b.src=r;if(e)if(e&&o){clearInterval(k);o.pause();t.removeEventListener("message",L,false);P.removeChild(a);a=s.createElement("iframe")}e=false;r=y._util.parseUri(r); +var f=r.queryKey,x,R=["api=1","player_id="+d,"title=0","byline=0","portrait=0"];b.loop=f.loop==="1"||b.loop;delete f.loop;b.autoplay=f.autoplay==="1"||b.autoplay;delete f.autoplay;r=h+"/video/"+/\d+$/.exec(r.path)+"?";for(x in f)f.hasOwnProperty(x)&&R.push(encodeURIComponent(x)+"="+encodeURIComponent(f[x]));r+=R.join("&");a.id=d;a.style.width="100%";a.style.height="100%";a.frameBorder=0;a.webkitAllowFullScreen=true;a.mozAllowFullScreen=true;a.allowFullScreen=true;P.appendChild(a);a.src=r;t.addEventListener("message", +E,false)}else{b.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};y.dispatchEvent("error")}}},autoplay:{get:function(){return b.autoplay},set:function(r){b.autoplay=y._util.isAttributeSet(r)}},loop:{get:function(){return b.loop},set:function(r){b.loop=y._util.isAttributeSet(r)}},width:{get:function(){return y.parentNode.offsetWidth}},height:{get:function(){return y.parentNode.offsetHeight}},currentTime:{get:function(){return b.currentTime}, +set:function(r){u(r)}},duration:{get:function(){return b.duration}},ended:{get:function(){return b.ended}},paused:{get:function(){return b.paused}},seeking:{get:function(){return b.seeking}},readyState:{get:function(){return b.readyState}},networkState:{get:function(){return b.networkState}},volume:{get:function(){return b.muted>0?b.muted:b.volume},set:function(r){if(r<0||r>1)throw"Volume value must be between 0.0 and 1.0";c(r)}},muted:{get:function(){return b.muted>0},set:function(r){I(y._util.isAttributeSet(r))}}, +error:{get:function(){return b.error}}});y._canPlaySrc=j.HTMLVimeoVideoElement._canPlaySrc;y.canPlayType=j.HTMLVimeoVideoElement.canPlayType;return y}var p=16,i="",h="https://player.vimeo.com";j.HTMLVimeoVideoElement=function(v){return new g(v)};j.HTMLVimeoVideoElement._canPlaySrc=function(v){return/player.vimeo.com\/video\/\d+/.test(v)||/vimeo.com\/\d+/.test(v)?"probably":i};j.HTMLVimeoVideoElement.canPlayType=function(v){return v==="video/x-vimeo"?"probably":i}})(Popcorn,window,document);(function(j,t,s){function m(){var E;if(YT.loaded)for(u=true;w.length;){E=w.shift();E()}else setTimeout(m,250)}function g(){var E;if(!B){if(t.YT)m();else{E=s.createElement("script");E.addEventListener("load",m,false);E.src="https://www.youtube.com/iframe_api";s.head.appendChild(E)}B=true}return u}function p(E){w.push(E)}function i(E){function L(G){W.push(G)}function H(){Q.pauseVideo();d("play",H);e("play",J)}function c(){e("pause",M);d("pause",c)}function I(){var G=function(){if(Q.isMuted()){e("play", +b);Q.playVideo()}else setTimeout(G,0)};V=true;Q.mute();G()}function y(G){var O={name:"MediaError"};switch(G.data){case 2:O.message="Invalid video parameter.";O.code=MediaError.MEDIA_ERR_ABORTED;break;case 5:O.message="The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.";O.code=MediaError.MEDIA_ERR_DECODE;case 100:O.message="Video not found.";O.code=MediaError.MEDIA_ERR_NETWORK;break;case 101:case 150:O.message="Video not usable.";O.code= +MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;break;default:O.message="Unknown error.";O.code=5}D.error=O;F.dispatchEvent("error")}function P(){e("play",J);e("pause",M);if(D.autoplay||!D.paused){d("play",P);D.paused=false;L(function(){D.paused||J()})}D.muted||Q.unMute();D.readyState=F.HAVE_METADATA;F.dispatchEvent("loadedmetadata");$=setInterval(k,h);F.dispatchEvent("loadeddata");D.readyState=F.HAVE_FUTURE_DATA;F.dispatchEvent("canplay");U=true;for(aa=setInterval(N,50);W.length;){W[0]();W.shift()}D.readyState= +F.HAVE_ENOUGH_DATA;F.dispatchEvent("canplaythrough")}function a(){d("pause",a);if(Q.getCurrentTime()>0)setTimeout(a,0);else if(D.autoplay||!D.paused){e("play",P);Q.playVideo()}else P()}function b(){d("play",b);if(Q.getCurrentTime()===0)setTimeout(b,0);else{e("pause",a);Q.seekTo(0);Q.pauseVideo()}}function e(G,O){F.addEventListener("youtube-"+G,O,false)}function d(G,O){F.removeEventListener("youtube-"+G,O,false)}function o(G){F.dispatchEvent("youtube-"+G)}function l(){D.networkState=F.NETWORK_LOADING; +var G=Q.getDuration();if(D.duration!==G){D.duration=G;F.dispatchEvent("durationchange")}F.dispatchEvent("waiting")}function q(G){switch(G.data){case YT.PlayerState.ENDED:o("ended");break;case YT.PlayerState.PLAYING:o("play");break;case YT.PlayerState.PAUSED:Q.getDuration()!==Q.getCurrentTime()&&o("pause");break;case YT.PlayerState.BUFFERING:o("buffering")}G.data!==YT.PlayerState.BUFFERING&&ba===YT.PlayerState.BUFFERING&&F.dispatchEvent("progress");ba=G.data}function C(G){if(F._canPlaySrc(G)){D.src= +G;if(g()){if(V)if(U){if(V&&Q){d("buffering",l);d("ended",z);d("play",J);d("pause",M);M();Y=U=false;D.currentTime=0;W=[];clearInterval($);clearInterval(aa);Q.stopVideo();Q.clearVideo();Q.destroy();T=s.createElement("div")}}else{L(function(){C(G)});return}K.appendChild(T);var O=F._util.parseUri(G).queryKey;delete O.v;D.autoplay=O.autoplay==="1"||D.autoplay;delete O.autoplay;D.loop=O.loop==="1"||D.loop;delete O.loop;O.rel=O.rel||0;O.modestbranding=O.modestbranding||1;O.iv_load_policy=O.iv_load_policy|| +3;O.disablekb=O.disablekb||1;O.showinfo=O.showinfo||0;var da=t.location.protocol==="file:"?"*":t.location.protocol+"//"+t.location.host;O.origin=O.origin||da;O.controls=O.controls||D.controls?2:0;D.controls=O.controls;O.wmode=O.wmode||"opaque";if(O.html5!==0)O.html5=1;G=A.exec(G)[1];Q=new YT.Player(T,{width:"100%",height:"100%",wmode:O.wmode,videoId:G,playerVars:O,events:{onReady:I,onError:y,onStateChange:q}});D.networkState=F.NETWORK_LOADING;F.dispatchEvent("loadstart");F.dispatchEvent("progress")}else p(function(){C(G)})}else{D.error= +{name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};F.dispatchEvent("error")}}function k(){var G=Q.getCurrentTime();if(D.seeking)n(G-D.currentTime)<1&&R();else{if(n(D.currentTime-G)>h){x();R()}D.currentTime=G}}function N(){var G=Q.getVideoLoadedFraction();if(G&&Z!==G){Z=G;F.dispatchEvent("progress")}}function r(G){if(G!==D.currentTime){D.currentTime=G;if(U){x();Q.seekTo(G)}else L(function(){x();Q.seekTo(G)})}}function f(){F.dispatchEvent("timeupdate")} +function x(){e("pause",c);d("pause",M);D.seeking=true;F.dispatchEvent("seeking")}function R(){D.ended=false;D.seeking=false;F.dispatchEvent("timeupdate");F.dispatchEvent("seeked");F.dispatchEvent("canplay");F.dispatchEvent("canplaythrough")}function J(){if(D.ended){r(0);D.ended=false}ca=setInterval(f,F._util.TIMEUPDATE_MS);D.paused=false;if(X){X=false;if(D.loop&&!Y||!D.loop){Y=true;F.dispatchEvent("play")}F.dispatchEvent("playing")}}function M(){D.paused=true;if(!X){X=true;clearInterval(ca);F.dispatchEvent("pause")}} +function z(){if(D.loop){r(0);F.play()}else{D.ended=true;M();e("play",H);d("play",J);F.dispatchEvent("timeupdate");F.dispatchEvent("ended")}}function S(G){D.muted=G;if(U){Q[G?"mute":"unMute"]();F.dispatchEvent("volumechange")}else L(function(){S(D.muted)})}if(!t.postMessage)throw"ERROR: HTMLYouTubeVideoElement requires window.postMessage";var F=new j._MediaElementProto,K=typeof E==="string"?s.querySelector(E):E,T=s.createElement("div"),D={src:v,networkState:F.NETWORK_EMPTY,readyState:F.HAVE_NOTHING, +seeking:false,autoplay:v,preload:v,controls:false,loop:false,poster:v,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},V=false,U=false,Y=false,Q,X=true,W=[],ba=-1,aa,Z=0,$,ca;F._eventNamespace=j.guid("HTMLYouTubeVideoElement::");F.parentNode=K;F._util.type="YouTube";e("buffering",l);e("ended",z);F.play=function(){D.paused=false;U?Q.playVideo():L(function(){F.play()})};F.pause=function(){D.paused=true;if(U){c();Q.pauseVideo()}else L(function(){F.pause()})};Object.defineProperties(F, +{src:{get:function(){return D.src},set:function(G){G&&G!==D.src&&C(G)}},autoplay:{get:function(){return D.autoplay},set:function(G){D.autoplay=F._util.isAttributeSet(G)}},loop:{get:function(){return D.loop},set:function(G){D.loop=F._util.isAttributeSet(G)}},width:{get:function(){return F.parentNode.offsetWidth}},height:{get:function(){return F.parentNode.offsetHeight}},currentTime:{get:function(){return D.currentTime},set:function(G){r(G)}},duration:{get:function(){return D.duration}},ended:{get:function(){return D.ended}}, +paused:{get:function(){return D.paused}},seeking:{get:function(){return D.seeking}},readyState:{get:function(){return D.readyState}},networkState:{get:function(){return D.networkState}},volume:{get:function(){return D.volume},set:function(G){if(G<0||G>1)throw"Volume value must be between 0.0 and 1.0";D.volume=G;if(U){Q.setVolume(D.volume*100);F.dispatchEvent("volumechange")}else L(function(){F.volume=G})}},muted:{get:function(){return D.muted},set:function(G){S(F._util.isAttributeSet(G))}},error:{get:function(){return D.error}}, +buffered:{get:function(){return{start:function(G){if(G===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(G){if(G===0){if(!D.duration)return 0;return D.duration*Z}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}},configurable:true}});F._canPlaySrc=j.HTMLYouTubeVideoElement._canPlaySrc;F.canPlayType=j.HTMLYouTubeVideoElement.canPlayType;return F}var h=10,v="",A=/^.*(?:\/|v=)(.{11})/,n=Math.abs,u=false,B=false,w=[];j.HTMLYouTubeVideoElement=function(E){return new i(E)};j.HTMLYouTubeVideoElement._canPlaySrc= +function(E){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu).*(?:\/|v=)(.{11})/.test(E)?"probably":v};j.HTMLYouTubeVideoElement.canPlayType=function(E){return E==="video/x-youtube"?"probably":v}})(Popcorn,window,document);(function(j){var t=function(s,m){var g=0,p=0,i;j.forEach(m.classes,function(h,v){i=[];if(h==="parent")i[0]=document.querySelectorAll("#"+m.target)[0].parentNode;else i=document.querySelectorAll("#"+m.target+" "+h);g=0;for(p=i.length;g");m.push(h("subtitle",v))}catch(u){for(;g< +p&&t[g];)g++}for(;g=0&&!A[u];)u--;B=u+1;for(h=0;h[\t ]*/);w.start=s(n[0]);v=n[1].indexOf(" ");if(v!==-1)n[1]=n[1].substr(0,v);for(w.end=s(n[1]);h/g,">");w.text=w.text.replace(/<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)>/gi,"<$1$3$7>");w.text=w.text.replace(/\\N/gi,"
");if(g&&g.target)w.target=g.target;i.push(t("subtitle", +w))}p.data=i;return p})})(Popcorn);(function(j){function t(g,p){var i=g.substr(10).split(","),h;h={start:s(i[p.start]),end:s(i[p.end])};if(h.start===-1||h.end===-1)throw"Invalid time";var v=B.call(n,/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,""),A=v.replace,n;n=i.length;B=[];for(var u=p.text;u");return h}function s(g){var p=g.split(":");if(g.length!==10||p.length<3)return-1;return parseInt(p[0],10)*3600+parseInt(p[1],10)*60+parseFloat(p[2],10)}function m(g, +p){var i={};i[g]=p;return i}j.parser("parseSSA",function(g){var p={title:"",remote:"",data:[]},i=[],h=0,v;g=g.text.split(/(?:\r\n|\r|\n)/gm);for(v=g.length;h");n.id=h.getAttribute("xml:id")||h.getAttribute("id");n.start=g(h.getAttribute("begin"),v); +n.end=g(h.getAttribute("end"),v);n.target=s(h,A);if(n.end<0){n.end=g(h.getAttribute("duration"),0);if(n.end>=0)n.end+=n.start;else n.end=Number.MAX_VALUE}return{subtitle:n}}function g(h,v){var A;if(!h)return-1;try{return j.util.toSeconds(h)}catch(n){for(var u=h.length-1;u>=0&&h[u]<="9"&&h[u]>="0";)u--;A=u;u=parseFloat(h.substring(0,A));A=h.substring(A);return u*({h:3600,m:60,s:1,ms:0.001}[A]||-1)+(v||0)}}var p=/^[\s]+|[\s]+$/gm,i=/(?:\r\n|\r|\n)/gm;j.parser("parseTTML",function(h){var v={title:"", +remote:"",data:[]};if(!h.xml||!h.xml.documentElement)return v;h=h.xml.documentElement.firstChild;if(!h)return v;for(;h.nodeName!=="body";)h=h.nextSibling;if(h)v.data=t(h,0);return v})})(Popcorn);(function(j){j.parser("parseTTXT",function(t){var s={title:"",remote:"",data:[]},m=function(v){v=v.split(":");var A=0;try{return parseFloat(v[0],10)*60*60+parseFloat(v[1],10)*60+parseFloat(v[2],10)}catch(n){A=0}return A},g=function(v,A){var n={};n[v]=A;return n};t=t.xml.lastChild.lastChild;for(var p=Number.MAX_VALUE,i=[];t;){if(t.nodeType===1&&t.nodeName==="TextSample"){var h={};h.start=m(t.getAttribute("sampleTime"));h.text=t.getAttribute("text");if(h.text){h.end=p-0.001;i.push(g("subtitle",h))}p= +h.start}t=t.previousSibling}s.data=i.reverse();return s})})(Popcorn);(function(j){function t(m){var g=m.split(":");m=m.length;var p;if(m!==12&&m!==9)throw"Bad cue";m=g.length-1;try{p=parseInt(g[m-1],10)*60+parseFloat(g[m],10);if(m===2)p+=parseInt(g[0],10)*3600}catch(i){throw"Bad cue";}return p}function s(m,g){var p={};p[m]=g;return p}j.parser("parseVTT",function(m){var g={title:"",remote:"",data:[]},p=[],i=0,h=0,v,A;m=m.text.split(/(?:\r\n|\r|\n)/gm);h=m.length;if(h===0||m[0]!=="WEBVTT")return g;for(i++;i")===-1)throw"Bad cue";n=u.replace(/--\>/," --\> ").split(/[\t ]+/);if(n.length<2)throw"Bad cue";B.id=u;B.start=t(n[0]);B.end=t(n[2]);for(A=B;i");p.push(s("subtitle",A))}catch(w){for(i=i;i0;s--)m[s].end>i&&d.removeTrackEvent(m[s]._id);for(m=0;mi&&d.removeTrackEvent(p[m]._id);d.data.trackEvents.byEnd.push({start:u,end:u});d.data.trackEvents.byStart.push({start:u,end:u})};d.media.addEventListener("durationchange", +d.data.durationChange,false)}if(d.options.frameAnimation){d.data.timeUpdate=function(){c.timeUpdate(d,{});c.forEach(c.manifest,function(i,u){if(n=d.data.running[u]){k=n.length;for(var p=0;p=1?j():d.media.addEventListener("loadedmetadata",j,false);return this}}};c.p.init.prototype=c.p;c.byId=function(a){for(var b=c.instances,e=b.length,d=0;d=0;e--){d=a.data.running[b][e];d._natives.end.call(a, +null,d);a.emit("trackend",c.extend({},d,{plugin:d.type,type:"trackend"}))}return a}},enable:function(a,b){if(a.data.disabled[b]){a.data.disabled[b]=false;if(b in c.registryByName&&a.data.running[b])for(var e=a.data.running[b].length-1,d;e>=0;e--){d=a.data.running[b][e];d._natives.start.call(a,null,d);a.emit("trackstart",c.extend({},d,{plugin:d.type,type:"trackstart",track:d}))}return a}},destroy:function(a){var b=a.data.events,e=a.data.trackEvents,d,g,h,j;for(g in b){d=b[g];for(h in d)delete d[h]; +b[g]=null}for(j in c.registryByName)c.removePlugin(a,j);e.byStart.length=0;e.byEnd.length=0;if(!a.isDestroyed){a.data.timeUpdate&&a.media.removeEventListener("timeupdate",a.data.timeUpdate,false);a.isDestroyed=true}c.instances.splice(c.instances.indexOf(a),1)}});c.guid.counter=1;c.extend(c.p,function(){var a={};c.forEach("load play pause currentTime playbackRate volume duration preload playbackRate autoplay loop controls muted buffered readyState seeking paused played seekable ended".split(/\s+/g), +function(b){a[b]=function(e){var d;if(typeof this.media[b]==="function"){if(e!=null&&/play|pause/.test(b))this.media.currentTime=c.util.toSeconds(e);this.media[b]();return this}if(e!=null){d=this.media[b];this.media[b]=e;d!==e&&this.emit("attrchange",{attribute:b,previousValue:d,currentValue:e});return this}return this.media[b]}});return a}());c.forEach("enable disable".split(" "),function(a){c.p[a]=function(b){return c[a](this,b)}});c.extend(c.p,{roundTime:function(){return Math.round(this.media.currentTime)}, +exec:function(a,b,e){var d=arguments.length,g="trackadded",h,j;try{j=c.util.toSeconds(a)}catch(l){}if(typeof j==="number")a=j;if(typeof a==="number"&&d===2){e=b;b=a;a=c.guid("cue")}else if(d===1)b=-1;else if(h=this.getTrackEvent(a)){this.data.trackEvents.remove(a);t.end(this,h);c.removeTrackEvent.ref(this,a);g="cuechange";if(typeof a==="string"&&d===2){if(typeof b==="number")e=h._natives.start;if(typeof b==="function"){e=b;b=h.start}}}else if(d>=2){if(typeof b==="string"){try{j=c.util.toSeconds(b)}catch(n){}b= +j}if(typeof b==="number")e=e||c.nop();if(typeof b==="function"){e=b;b=-1}}d={id:a,start:b,end:b+1,_running:false,_natives:{start:e||c.nop,end:c.nop,type:"cue"}};if(h)d=c.extend(h,d);if(g==="cuechange"){d._id=d.id||d._id||c.guid(d._natives.type);this.data.trackEvents.add(d);t.start(this,d);this.timeUpdate(this,null,true);c.addTrackEvent.ref(this,d);this.emit(g,c.extend({},d,{id:a,type:g,previousValue:{time:h.start,fn:h._natives.start},currentValue:{time:b,fn:e||c.nop},track:h}))}else c.addTrackEvent(this, +d);return this},mute:function(a){a=a==null||a===true?"muted":"unmuted";if(a==="unmuted"){this.media.muted=false;this.media.volume=this.data.state.volume}if(a==="muted"){this.data.state.volume=this.media.volume;this.media.muted=true}this.emit(a);return this},unmute:function(a){return this.mute(a==null?false:!a)},position:function(){return c.position(this.media)},toggle:function(a){return c[this.data.disabled[a]?"enable":"disable"](this,a)},defaults:function(a,b){if(c.isArray(a)){c.forEach(a,function(e){for(var d in e)this.defaults(d, +e[d])},this);return this}if(!this.options.defaults)this.options.defaults={};this.options.defaults[a]||(this.options.defaults[a]={});c.extend(this.options.defaults[a],b);return this}});c.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"}; +c.Events.Natives=c.Events.UIEvents+" "+c.Events.MouseEvents+" "+c.Events.Events;x.events.apiTypes=["UIEvents","MouseEvents","Events"];(function(a,b){for(var e=x.events.apiTypes,d=a.Natives.split(/\s+/g),g=0,h=d.length;g-1&&this.media.addEventListener(a,function(l){if(e.data.events[a])for(h=e.data.events[a].slice();h.length;)h.shift().call(e,l)},false);return this},unlisten:function(a,b){var e,d=this.data.events[a];if(d){if(typeof b=== +"string"){for(e=0;ea.media.currentTime&&b.start<=a.media.currentTime&&!b._running){b._running=true;a.data.running[b._natives.type].push(b);if(!a.data.disabled[b._natives.type]){b._natives.start.call(a,null,b);a.emit("trackstart",c.extend({},b,{plugin:b._natives.type,type:"trackstart",track:b}))}}};t.end=function(a,b){var e;if((b.end<=a.media.currentTime||b.start> +a.media.currentTime)&&b._running){e=a.data.running[b._natives.type];b._running=false;e.splice(e.indexOf(b),1);if(!a.data.disabled[b._natives.type]){b._natives.end.call(a,null,b);a.emit("trackend",c.extend({},b,{plugin:b._natives.type,type:"trackend",track:b}))}}};y.prototype.where=function(a){return(this.parent.getTrackEvents()||[]).filter(function(b){var e,d;if(!a)return true;for(e in a){d=a[e];if(b[e]&&b[e]===d||b._natives[e]&&b._natives[e]===d)return true}return false})};y.prototype.add=function(a){var b= +this.byStart,e=this.byEnd,d;a&&a._id&&this.parent.data.history.push(a._id);a.start=c.util.toSeconds(a.start,this.parent.options.framerate);a.end=c.util.toSeconds(a.end,this.parent.options.framerate);for(d=b.length-1;d>=0;d--)if(a.start>=b[d].start){b.splice(d+1,0,a);break}for(b=e.length-1;b>=0;b--)if(a.end>e[b].end){e.splice(b+1,0,a);break}d<=this.parent.data.trackEvents.startIndex&&a.start<=this.parent.data.trackEvents.previousUpdateTime&&this.parent.data.trackEvents.startIndex++;b<=this.parent.data.trackEvents.endIndex&& +a.end-1;){b=this.byStart[g];e=this.byEnd[g];if(!b._id){j.push(b);l.push(e)}if(b._id){b._id!==a&&j.push(b);e._id!==a&&l.push(e);if(b._id===a)h=g}g++}d=this.animating.length; +g=0;if(d)for(;--d>-1;){b=this.animating[g];b._id||n.push(b);b._id&&b._id!==a&&n.push(b);g++}h<=this.startIndex&&this.startIndex--;h<=this.endIndex&&this.endIndex--;this.byStart=j;this.byEnd=l;this.animating=n;this.count--;d=this.parent.data.history.length;for(g=0;ge&&k._running===false){k._running=true;a.data.running[f].push(k);if(!a.data.disabled[f]){d.start.call(a,b, +k);a.emit("trackstart",c.extend({},k,{plugin:f,type:"trackstart",track:k}))}}j++}else{c.removeTrackEvent(a,k._id);return}}}else if(d>e){for(;g.byStart[j]&&g.byStart[j].start>e;){k=g.byStart[j];f=(d=k._natives)&&d.type;if(!d||o[f]||a[f]){if(k._running===true){k._running=false;i=a.data.running[f];i.splice(i.indexOf(k),1);if(!a.data.disabled[f]){d.end.call(a,b,k);a.emit("trackend",c.extend({},k,{plugin:f,type:"trackend",track:k}))}}j--}else{c.removeTrackEvent(a,k._id);return}}for(;g.byEnd[h]&&g.byEnd[h].end> +e;){k=g.byEnd[h];f=(d=k._natives)&&d.type;if(!d||o[f]||a[f]){if(k.start<=e&&k._running===false){k._running=true;a.data.running[f].push(k);if(!a.data.disabled[f]){d.start.call(a,b,k);a.emit("trackstart",c.extend({},k,{plugin:f,type:"trackstart",track:k}))}}h--}else{c.removeTrackEvent(a,k._id);return}}}g.endIndex=h;g.startIndex=j;g.previousUpdateTime=e;g.byStart.length= +0)c.error("'"+a+"' is a protected function name");else{var d=typeof b==="function",g=["start","end","type","manifest"],h=["_setup","_teardown","start","end","frame"],j={},l=function(k,f){k=k||c.nop;f=f||c.nop;return function(){k.apply(this,arguments);f.apply(this,arguments)}};c.manifest[a]=e=e||b.manifest||{};h.forEach(function(k){b[k]=H(b[k]||c.nop,a)});var n=function(k,f){if(!f)return this;if(f.ranges&&c.isArray(f.ranges)){c.forEach(f.ranges,function(m){m=c.extend({},f,m);delete m.ranges;this[a](m)}, +this);return this}var i=f._natives={},u="",p;c.extend(i,k);f._natives.type=f._natives.plugin=a;f._running=false;i.start=i.start||i["in"];i.end=i.end||i.out;if(f.once)i.end=l(i.end,function(){this.removeTrackEvent(f._id)});i._teardown=l(function(){var m=C.call(arguments),s=this.data.running[i.type];m.unshift(null);m[1]._running&&s.splice(s.indexOf(f),1)&&i.end.apply(this,m);m[1]._running=false;this.emit("trackend",c.extend({},f,{plugin:i.type,type:"trackend",track:c.getTrackEvent(this,f.id||f._id)}))}, +i._teardown);i._teardown=l(i._teardown,function(){this.emit("trackteardown",c.extend({},f,{plugin:a,type:"trackteardown",track:c.getTrackEvent(this,f.id||f._id)}))});f.compose=f.compose||[];if(typeof f.compose==="string")f.compose=f.compose.split(" ");f.effect=f.effect||[];if(typeof f.effect==="string")f.effect=f.effect.split(" ");f.compose=f.compose.concat(f.effect);f.compose.forEach(function(m){u=c.compositions[m]||{};h.forEach(function(s){i[s]=l(i[s],u[s])})});f._natives.manifest=e;if(!("start"in +f))f.start=f["in"]||0;if(!f.end&&f.end!==0)f.end=f.out||Number.MAX_VALUE;if(!v.call(f,"toString"))f.toString=function(){var m=["start: "+f.start,"end: "+f.end,"id: "+(f.id||f._id)];f.target!=null&&m.push("target: "+f.target);return a+" ( "+m.join(", ")+" )"};if(!f.target){p="options"in e&&e.options;f.target=p&&"target"in p&&p.target}if(!f._id&&f._natives)f._id=c.guid(f._natives.type);if(f instanceof t){if(f._natives){f._id=f.id||f._id||c.guid(f._natives.type);if(f._natives._setup){f._natives._setup.call(this, +f);this.emit("tracksetup",c.extend({},f,{plugin:f._natives.type,type:"tracksetup",track:f}))}}this.data.trackEvents.add(f);t.start(this,f);this.timeUpdate(this,null,true);f._id&&c.addTrackEvent.ref(this,f)}else c.addTrackEvent(this,f);c.forEach(k,function(m,s){g.indexOf(s)===-1&&this.on(s,m)},this);return this};c.p[a]=j[a]=function(k,f){var i,u;if(k&&!f)f=k;else if(i=this.getTrackEvent(k)){u=f;var p={},m;for(m in i)if(v.call(u,m)&&v.call(i,m))p[m]=i[m];if(i._natives._update){this.data.trackEvents.remove(i); +if(v.call(f,"start"))i.start=f.start;if(v.call(f,"end"))i.end=f.end;t.end(this,i);d&&b.call(this,i);i._natives._update.call(this,i,f);this.data.trackEvents.add(i);t.start(this,i)}else{c.extend(i,f);this.data.trackEvents.remove(k);i._natives._teardown&&i._natives._teardown.call(this,i);c.removeTrackEvent.ref(this,k);if(d)n.call(this,b.call(this,i),i);else{i._id=i.id||i._id||c.guid(i._natives.type);if(i._natives&&i._natives._setup){i._natives._setup.call(this,i);this.emit("tracksetup",c.extend({},i, +{plugin:i._natives.type,type:"tracksetup",track:i}))}this.data.trackEvents.add(i);t.start(this,i);this.timeUpdate(this,null,true);c.addTrackEvent.ref(this,i)}this.emit("trackchange",{id:i.id,type:"trackchange",previousValue:p,currentValue:i,track:i});return this}i._natives.type!=="cue"&&this.emit("trackchange",{id:i.id,type:"trackchange",previousValue:p,currentValue:u,track:i});return this}else f.id=k;this.data.running[a]=this.data.running[a]||[];i=c.extend({},this.options.defaults&&this.options.defaults[a]|| +{},f);n.call(this,d?b.call(this,i):b,i);return this};e&&c.extend(b,{manifest:e});var o={fn:j[a],definition:b,base:b,parents:[],name:a};c.registry.push(c.extend(j,o,{type:a}));c.registryByName[a]=o;return j}};c.plugin.errors=[];c.plugin.debug=c.version==="d05323f";c.removePlugin=function(a,b){if(!b){b=a;a=c.p;if(c.protect.natives.indexOf(b.toLowerCase())>=0){c.error("'"+b+"' is a protected function name");return}var e=c.registry.length,d;for(d=0;d-1){g=g.split(";");h=0;if(b&&typeof b==="number")h=parseFloat(g[1],10)/b;e[d]=parseInt(g[0],10)+h}d=e[0];return{1:parseFloat(d,10),2:parseInt(d,10)*60+parseFloat(e[1],10),3:parseInt(d,10)*3600+parseInt(e[1],10)*60+parseFloat(e[2],10)}[e.length||1]}};c.p.cue=c.p.exec;c.protect={natives:function(a){return Object.keys?Object.keys(a):function(b){var e,d=[];for(e in b)v.call(b,e)&&d.push(e);return d}(a)}(c.p).map(function(a){return a.toLowerCase()})}; +c.forEach({listen:"on",unlisten:"off",trigger:"emit",exec:"cue"},function(a,b){var e=c.p[b];c.p[b]=function(){if(typeof console!=="undefined"&&console.warn){console.warn("Deprecated method '"+b+"', "+(a==null?"do not use.":"use '"+a+"' instead."));c.p[b]=e}return c.p[a].apply(this,[].slice.call(arguments))}});r.Popcorn=c}else{r.Popcorn={isSupported:false};for(w="byId forEach extend effects error guid sizeOf isArray nop position disable enable destroyaddTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId timeUpdate plugin removePlugin compose effect xhr getJSONP getScript".split(/\s+/);w.length;)r.Popcorn[w.shift()]= +function(){}}})(window,window.document); diff --git a/dist/popcorn.wrappers.js b/dist/popcorn.wrappers.js new file mode 100644 index 000000000..1637ea301 --- /dev/null +++ b/dist/popcorn.wrappers.js @@ -0,0 +1,3449 @@ +/** + * The Popcorn._MediaElementProto object is meant to be used as a base + * prototype for HTML*VideoElement and HTML*AudioElement wrappers. + * MediaElementProto requires that users provide: + * - parentNode: the element owning the media div/iframe + * - _eventNamespace: the unique namespace for all events + */ +(function( Popcorn, document ) { + + /********************************************************************************* + * parseUri 1.2.2 + * http://blog.stevenlevithan.com/archives/parseuri + * (c) Steven Levithan + * MIT License + */ + function parseUri (str) { + var o = parseUri.options, + m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), + uri = {}, + i = 14; + + while (i--) { + uri[o.key[i]] = m[i] || ""; + } + + uri[o.q.name] = {}; + uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { + if ($1) { + uri[o.q.name][$1] = $2; + } + }); + + return uri; + } + + parseUri.options = { + strictMode: false, + key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], + q: { + name: "queryKey", + parser: /(?:^|&)([^&=]*)=?([^&]*)/g + }, + parser: { + strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, + loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ + } + }; + /*********************************************************************************/ + + // Fake a TimeRanges object + var _fakeTimeRanges = { + length: 0, + start: Popcorn.nop, + end: Popcorn.nop + }; + + // Make sure the browser has MediaError + window.MediaError = window.MediaError || (function() { + function MediaError(code, msg) { + this.code = code || null; + this.message = msg || ""; + } + MediaError.MEDIA_ERR_NONE_ACTIVE = 0; + MediaError.MEDIA_ERR_ABORTED = 1; + MediaError.MEDIA_ERR_NETWORK = 2; + MediaError.MEDIA_ERR_DECODE = 3; + MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; + + return MediaError; + }()); + + + function MediaElementProto() { + var protoElement = {}, + events = {}, + parentNode; + if ( !Object.prototype.__defineGetter__ ) { + protoElement = document.createElement( "div" ); + } + protoElement._util = { + // Each wrapper stamps a type. + type: "HTML5", + + // How often to trigger timeupdate events + TIMEUPDATE_MS: 250, + + // Standard width and height + MIN_WIDTH: 300, + MIN_HEIGHT: 150, + + // Check for attribute being set or value being set in JS. The following are true: + // autoplay + // autoplay="true" + // v.autoplay=true; + isAttributeSet: function( value ) { + return ( typeof value === "string" || value === true ); + }, + + parseUri: parseUri + }; + // Mimic DOM events with custom, namespaced events on the document. + // Each media element using this prototype needs to provide a unique + // namespace for all its events via _eventNamespace. + protoElement.addEventListener = function( type, listener, useCapture ) { + document.addEventListener( this._eventNamespace + type, listener, useCapture ); + }; + + protoElement.removeEventListener = function( type, listener, useCapture ) { + document.removeEventListener( this._eventNamespace + type, listener, useCapture ); + }; + + protoElement.dispatchEvent = function( name ) { + var customEvent = document.createEvent( "CustomEvent" ), + detail = { + type: name, + target: this.parentNode, + data: null + }; + + customEvent.initCustomEvent( this._eventNamespace + name, false, false, detail ); + document.dispatchEvent( customEvent ); + }; + + protoElement.load = Popcorn.nop; + + protoElement.canPlayType = function( url ) { + return ""; + }; + + // Popcorn expects getBoundingClientRect to exist, forward to parent node. + protoElement.getBoundingClientRect = function() { + return parentNode.getBoundingClientRect(); + }; + + protoElement.NETWORK_EMPTY = 0; + protoElement.NETWORK_IDLE = 1; + protoElement.NETWORK_LOADING = 2; + protoElement.NETWORK_NO_SOURCE = 3; + + protoElement.HAVE_NOTHING = 0; + protoElement.HAVE_METADATA = 1; + protoElement.HAVE_CURRENT_DATA = 2; + protoElement.HAVE_FUTURE_DATA = 3; + protoElement.HAVE_ENOUGH_DATA = 4; + Object.defineProperties( protoElement, { + + currentSrc: { + get: function() { + return this.src !== undefined ? this.src : ""; + }, + configurable: true + }, + + parentNode: { + get: function() { + return parentNode; + }, + set: function( val ) { + parentNode = val; + }, + configurable: true + }, + + // We really can't do much more than "auto" with most of these. + preload: { + get: function() { + return "auto"; + }, + set: Popcorn.nop, + configurable: true + }, + + controls: { + get: function() { + return true; + }, + set: Popcorn.nop, + configurable: true + }, + + // TODO: it would be good to overlay an using this URL + poster: { + get: function() { + return ""; + }, + set: Popcorn.nop, + configurable: true + }, + + crossorigin: { + get: function() { + return ""; + }, + configurable: true + }, + + played: { + get: function() { + return _fakeTimeRanges; + }, + configurable: true + }, + + seekable: { + get: function() { + return _fakeTimeRanges; + }, + configurable: true + }, + + buffered: { + get: function() { + return _fakeTimeRanges; + }, + configurable: true + }, + + defaultMuted: { + get: function() { + return false; + }, + configurable: true + }, + + defaultPlaybackRate: { + get: function() { + return 1.0; + }, + configurable: true + }, + + style: { + get: function() { + return this.parentNode.style; + }, + configurable: true + }, + + id: { + get: function() { + return this.parentNode.id; + }, + configurable: true + } + + // TODO: + // initialTime + // playbackRate + // startOffsetTime + + }); + return protoElement; + } + + Popcorn._MediaElementProto = MediaElementProto; + +}( Popcorn, window.document )); +/** + * The HTMLVideoElement and HTMLAudioElement are wrapped media elements + * that are created within a DIV, and forward their properties and methods + * to a wrapped object. + */ +(function( Popcorn, document ) { + + function canPlaySrc( src ) { + // We can't really know based on URL. + return "maybe"; + } + + function wrapMedia( id, mediaType ) { + var parent = typeof id === "string" ? document.querySelector( id ) : id, + media = document.createElement( mediaType ); + + parent.appendChild( media ); + + // Add the helper function _canPlaySrc so this works like other wrappers. + media._canPlaySrc = canPlaySrc; + + return media; + } + + Popcorn.HTMLVideoElement = function( id ) { + return wrapMedia( id, "video" ); + }; + Popcorn.HTMLVideoElement._canPlaySrc = canPlaySrc; + + + Popcorn.HTMLAudioElement = function( id ) { + return wrapMedia( id, "audio" ); + }; + Popcorn.HTMLAudioElement._canPlaySrc = canPlaySrc; + +}( Popcorn, window.document )); +(function( Popcorn, window, document ) { + + var + + EMPTY_STRING = "", + + jwReady = false, + jwLoaded = false, + jwCallbacks = []; + + function onJWPlayerAPIReady() { + jwReady = true; + var i = jwCallbacks.length; + while( i-- ) { + jwCallbacks[ i ](); + delete jwCallbacks[ i ]; + } + }; + + function jwplayerReadyCheck() { + if ( window.jwplayer ) { + onJWPlayerAPIReady(); + } else { + setTimeout( jwplayerReadyCheck, 100 ); + } + } + + function isJWPlayerReady() { + // If the jwplayer API isn't injected, do it now. + if ( !jwLoaded ) { + if ( !window.jwplayer ) { + var tag = document.createElement( "script" ); + + tag.src = "https://jwpsrv.com/library/zaIF4JI9EeK2FSIACpYGxA.js"; + var firstScriptTag = document.getElementsByTagName( "script" )[ 0 ]; + firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); + } + jwLoaded = true; + jwplayerReadyCheck(); + } + return jwReady; + } + + function addJWPlayerCallback( callback ) { + jwCallbacks.unshift( callback ); + } + + function HTMLJWPlayerVideoElement( id ) { + + if ( !window.postMessage ) { + throw "ERROR: HTMLJWPlayerVideoElement requires window.postMessage"; + } + + var self = new Popcorn._MediaElementProto(), + parent = typeof id === "string" ? document.querySelector( id ) : id, + impl = { + src: EMPTY_STRING, + networkState: self.NETWORK_EMPTY, + readyState: self.HAVE_NOTHING, + seeking: false, + autoplay: EMPTY_STRING, + preload: EMPTY_STRING, + controls: false, + loop: false, + poster: EMPTY_STRING, + volume: 1, + muted: false, + currentTime: 0, + duration: NaN, + ended: false, + paused: true, + error: null + }, + playerReady = false, + catchRoguePauseEvent = false, + catchRoguePlayEvent = false, + mediaReady = false, + loopedPlay = false, + player, + playerPaused = true, + mediaReadyCallbacks = [], + playerState = -1, + lastLoadedFraction = 0, + firstPlay = true, + firstPause = false; + + // Namespace all events we'll produce + self._eventNamespace = Popcorn.guid( "HTMLJWPlayerVideoElement::" ); + + self.parentNode = parent; + + // Mark this as JWPlayer + self._util.type = "JWPlayer"; + + function addMediaReadyCallback( callback ) { + mediaReadyCallbacks.unshift( callback ); + } + + function waitForMetaData(){ + var duration = player.getDuration(); + //JWPlayer sets the duration only after the video has started playing + //Hence, we assume that when duration is available all + //other metadata is also ready + if(duration == -1 || duration == undefined){ + setTimeout(waitForMetaData, 0); + } else { + impl.duration = duration + self.dispatchEvent( "durationchange" ); + playerReady = true; + impl.readyState = self.HAVE_METADATA; + self.dispatchEvent( "loadedmetadata" ); + self.dispatchEvent( "loadeddata" ); + + impl.readyState = self.HAVE_FUTURE_DATA; + self.dispatchEvent( "canplay" ); + + mediaReady = true; + + var i = 0; + while( mediaReadyCallbacks.length ) { + mediaReadyCallbacks[ i ](); + mediaReadyCallbacks.shift(); + } + // We can't easily determine canplaythrough, but will send anyway. + impl.readyState = self.HAVE_ENOUGH_DATA; + self.dispatchEvent( "canplaythrough" ); + } + } + + function onReady() { + // JWPlayer needs a play/pause to force ready state. + waitForMetaData(); + } + + // TODO: (maybe) + // JWPlayer events cannot be removed, so we use functions inside the event. + // This way we can change these functions to "remove" events. + function onPauseEvent() { + if ( catchRoguePauseEvent ) { + catchRoguePauseEvent = false; + } else if ( firstPause ) { + firstPause = false; + onReady(); + } else { + onPause(); + } + } + function onPlayEvent() { + if ( firstPlay ) { + // fake ready event + firstPlay = false; + + // Set initial paused state + if ( impl.autoplay || !impl.paused ) { + impl.paused = false; + addMediaReadyCallback( onPlay ); + onReady(); + } else { + firstPause = true; + catchRoguePlayEvent = true; + player.pause( true ); + } + } else if ( catchRoguePlayEvent ) { + catchRoguePlayEvent = false; + catchRoguePauseEvent = true; + // Repause without triggering any events. + player.pause( true ); + } else { + onPlay(); + } + } + + function onSeekEvent() { + if ( impl.seeking ) { + onSeeked(); + } + } + + function onPlayerReady() { + player.onPause( onPauseEvent ); + player.onTime(function() { + if ( !impl.ended && !impl.seeking ) { + impl.currentTime = player.getPosition(); + self.dispatchEvent( "timeupdate" ); + } + }); + player.onSeek( onSeekEvent ); + player.onPlay(function() { + if ( !impl.ended ) { + onPlayEvent(); + } + }); + player.onBufferChange( onProgress ); + player.onComplete( onEnded ); + player.play( true ); + } + + function getDuration() { + return player.getDuration(); + } + + function onPlayerError( e ) { + var err = { name: "MediaError" }; + err.message = e.message; + err.code = e.code || 5; + + impl.error = err; + self.dispatchEvent( "error" ); + } + + function destroyPlayer() { + if ( !( playerReady && player ) ) { + return; + } + + player.destroy(); + } + + function changeSrc( aSrc ) { + if ( !self._canPlaySrc( aSrc ) ) { + impl.error = { + name: "MediaError", + message: "Media Source Not Supported", + code: MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + }; + self.dispatchEvent( "error" ); + return; + } + + // Use any player vars passed on the URL + var playerVars = self._util.parseUri( aSrc ).queryKey; + + // Show/hide controls. Sync with impl.controls and prefer URL value. + impl.controls = playerVars.controls = playerVars.controls || impl.controls; + + impl.src = aSrc; + + // Make sure JWPlayer is ready, and if not, register a callback + if ( !isJWPlayerReady() ) { + addJWPlayerCallback( function() { changeSrc( aSrc ); } ); + return; + } + + if ( playerReady ) { + destroyPlayer(); + } + + var params = { + width: "100%", + height: "100%", + autostart: impl.autoplay, + controls: impl.controls + }; + + // Source can either be a single file or multiple files that represent + // different quality + if(typeof aSrc == "string"){ + params["file"] = aSrc; + } else { + params["sources"] = aSrc; + } + + jwplayer( parent.id ).setup(params); + + player = jwplayer( parent.id ); + player.onReady( onPlayerReady ); + player.onError( onPlayerError ); + jwplayer.utils.log = function( msg, obj ) { + if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) { + if ( obj ) { + console.log( msg, obj ); + } else { + console.log( msg ); + } + } + + if ( msg === "No suitable players found and fallback enabled" ) { + onPlayerError({ + message: msg, + code: 4 + }); + } + }; + + impl.networkState = self.NETWORK_LOADING; + self.dispatchEvent( "loadstart" ); + self.dispatchEvent( "progress" ); + } + + function getCurrentTime() { + return impl.currentTime; + } + + function changeCurrentTime( aTime ) { + impl.currentTime = aTime; + if ( !mediaReady ) { + addMediaReadyCallback( function() { + onSeeking(); + player.seek( aTime ); + }); + return; + } + + onSeeking(); + player.seek( aTime ); + } + + function onSeeking() { + impl.seeking = true; + // jwplayer plays right after a seek, we do not want this. + if ( impl.paused ) { + catchRoguePlayEvent = true; + } + self.dispatchEvent( "seeking" ); + } + + function onSeeked() { + impl.ended = false; + impl.seeking = false; + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "seeked" ); + self.dispatchEvent( "canplay" ); + self.dispatchEvent( "canplaythrough" ); + } + + function onPlay() { + impl.paused = false; + + if ( playerReady && playerPaused ) { + playerPaused = false; + + // Only 1 play when video.loop=true + if ( ( impl.loop && !loopedPlay ) || !impl.loop ) { + loopedPlay = true; + self.dispatchEvent( "play" ); + } + self.dispatchEvent( "playing" ); + } + } + + function onProgress() { + self.dispatchEvent( "progress" ); + } + + self.play = function() { + self.dispatchEvent( "play" ); + impl.paused = false; + if ( !mediaReady ) { + addMediaReadyCallback( function() { self.play(); } ); + return; + } + if ( impl.ended ) { + changeCurrentTime( 0 ); + impl.ended = false; + } + player.play( true ); + }; + + function onPause() { + impl.paused = true; + if ( !playerPaused ) { + playerPaused = true; + self.dispatchEvent( "pause" ); + } + } + + self.pause = function() { + impl.paused = true; + if ( !mediaReady ) { + addMediaReadyCallback( function() { self.pause(); } ); + return; + } + player.pause( true ); + }; + + function onEnded() { + if ( impl.loop ) { + changeCurrentTime( 0 ); + } else { + impl.ended = true; + onPause(); + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "ended" ); + } + } + + function setVolume( aValue ) { + impl.volume = aValue; + if ( !mediaReady ) { + addMediaReadyCallback( function() { + setVolume( impl.volume ); + }); + return; + } + player.setVolume( impl.volume * 100 ); + self.dispatchEvent( "volumechange" ); + } + + function setMuted( aValue ) { + impl.muted = aValue; + if ( !mediaReady ) { + addMediaReadyCallback( function() { setMuted( impl.muted ); } ); + return; + } + player.setMute( aValue ); + self.dispatchEvent( "volumechange" ); + } + + function getMuted() { + return impl.muted; + } + + Object.defineProperties( self, { + + src: { + get: function() { + return impl.src; + }, + set: function( aSrc ) { + if ( aSrc && aSrc !== impl.src ) { + changeSrc( aSrc ); + } + } + }, + + autoplay: { + get: function() { + return impl.autoplay; + }, + set: function( aValue ) { + impl.autoplay = self._util.isAttributeSet( aValue ); + } + }, + + loop: { + get: function() { + return impl.loop; + }, + set: function( aValue ) { + impl.loop = self._util.isAttributeSet( aValue ); + } + }, + + width: { + get: function() { + return self.parentNode.offsetWidth; + } + }, + + height: { + get: function() { + return self.parentNode.offsetHeight; + } + }, + + currentTime: { + get: function() { + return getCurrentTime(); + }, + set: function( aValue ) { + changeCurrentTime( aValue ); + } + }, + + duration: { + get: function() { + return getDuration(); + } + }, + + ended: { + get: function() { + return impl.ended; + } + }, + + paused: { + get: function() { + return impl.paused; + } + }, + + seeking: { + get: function() { + return impl.seeking; + } + }, + + readyState: { + get: function() { + return impl.readyState; + } + }, + + networkState: { + get: function() { + return impl.networkState; + } + }, + + volume: { + get: function() { + return impl.volume; + }, + set: function( aValue ) { + if ( aValue < 0 || aValue > 1 ) { + throw "Volume value must be between 0.0 and 1.0"; + } + + setVolume( aValue ); + } + }, + + muted: { + get: function() { + return impl.muted; + }, + set: function( aValue ) { + setMuted( self._util.isAttributeSet( aValue ) ); + } + }, + + error: { + get: function() { + return impl.error; + } + }, + + buffered: { + get: function () { + var timeRanges = { + start: function( index ) { + if ( index === 0 ) { + return 0; + } + + //throw fake DOMException/INDEX_SIZE_ERR + throw "INDEX_SIZE_ERR: DOM Exception 1"; + }, + end: function( index ) { + var duration; + if ( index === 0 ) { + duration = getDuration(); + if ( !duration ) { + return 0; + } + + return duration * ( player.getBuffer() / 100 ); + } + + //throw fake DOMException/INDEX_SIZE_ERR + throw "INDEX_SIZE_ERR: DOM Exception 1"; + }, + length: 1 + }; + + return timeRanges; + } + } + }); + + self._canPlaySrc = Popcorn.HTMLJWPlayerVideoElement._canPlaySrc; + self.canPlayType = Popcorn.HTMLJWPlayerVideoElement.canPlayType; + + return self; + } + + Popcorn.HTMLJWPlayerVideoElement = function( id ) { + return new HTMLJWPlayerVideoElement( id ); + }; + + // Helper for identifying URLs we know how to play. + Popcorn.HTMLJWPlayerVideoElement._canPlaySrc = function( source ) { + // Because of the nature of JWPlayer playing all media types, + // it can potentially play all url formats. + if(typeof source == "string"){ + if(/.+\.+/g.exec(source)){ + return "probably"; + } + } else { + return "probably" + } + }; + + // This could potentially support everything. It is a bit of a catch all player. + Popcorn.HTMLJWPlayerVideoElement.canPlayType = function( type ) { + return "probably"; + }; + +}( Popcorn, window, document )); +/** + * Simplified Media Fragments (http://www.w3.org/TR/media-frags/) Null player. + * Valid URIs include: + * + * #t=,100 -- a null video of 100s + * #t=5,100 -- a null video of 100s, which starts at 5s (i.e., 95s duration) + * + */ +(function( Popcorn, document ) { + + var + + // How often (ms) to update the video's current time, + // and by how much (s). + DEFAULT_UPDATE_RESOLUTION_MS = 16, + DEFAULT_UPDATE_RESOLUTION_S = DEFAULT_UPDATE_RESOLUTION_MS / 1000, + + EMPTY_STRING = "", + + // We currently support simple temporal fragments: + // #t=,100 -- a null video of 100s (starts at 0s) + // #t=5,100 -- a null video of 100s, which starts at 5s (i.e., 95s duration) + temporalRegex = /#t=(\d+\.?\d*)?,?(\d+\.?\d*)/; + + function NullPlayer( options ) { + this.startTime = 0; + this.currentTime = options.currentTime || 0; + this.duration = options.duration || NaN; + this.playInterval = null; + this.paused = true; + this.defaultPlaybackRate = 1; + this.playbackRate = 1; + this.ended = options.endedCallback || Popcorn.nop; + } + + function nullPlay( video ) { + video.currentTime += ( Date.now() - video.startTime ) / (1000 / video.playbackRate); + video.startTime = Date.now(); + if( video.currentTime >= video.duration ) { + video.pause(video.duration); + video.ended(); + } + if( video.currentTime < 0 ) { + video.pause(0); + } + } + + NullPlayer.prototype = { + + play: function() { + var video = this; + if ( this.paused ) { + this.paused = false; + this.startTime = Date.now(); + this.playInterval = setInterval( function() { nullPlay( video ); }, + DEFAULT_UPDATE_RESOLUTION_MS ); + } + }, + + pause: function() { + if ( !this.paused ) { + this.paused = true; + clearInterval( this.playInterval ); + } + }, + + seekTo: function( aTime ) { + aTime = aTime < 0 ? 0 : aTime; + aTime = aTime > this.duration ? this.duration : aTime; + this.currentTime = aTime; + } + + }; + + function HTMLNullVideoElement( id ) { + + var self = new Popcorn._MediaElementProto(), + parent = typeof id === "string" ? document.querySelector( id ) : id, + elem = document.createElement( "div" ), + playerReady = false, + player, + impl = { + src: EMPTY_STRING, + networkState: self.NETWORK_EMPTY, + readyState: self.HAVE_NOTHING, + autoplay: EMPTY_STRING, + preload: EMPTY_STRING, + controls: EMPTY_STRING, + loop: false, + poster: EMPTY_STRING, + volume: 1, + muted: false, + width: parent.width|0 ? parent.width : self._util.MIN_WIDTH, + height: parent.height|0 ? parent.height : self._util.MIN_HEIGHT, + seeking: false, + ended: false, + paused: 1, // 1 vs. true to differentiate first time access + error: null + }, + playerReadyCallbacks = [], + timeUpdateInterval; + + // Namespace all events we'll produce + self._eventNamespace = Popcorn.guid( "HTMLNullVideoElement::" ); + + // Attach parentNode + self.parentNode = parent; + + // Mark type as NullVideo + self._util.type = "NullVideo"; + + function addPlayerReadyCallback( callback ) { + playerReadyCallbacks.push( callback ); + } + + function onPlayerReady() { + var callback; + playerReady = true; + + impl.networkState = self.NETWORK_IDLE; + impl.readyState = self.HAVE_METADATA; + self.dispatchEvent( "loadedmetadata" ); + + self.dispatchEvent( "loadeddata" ); + + impl.readyState = self.HAVE_FUTURE_DATA; + self.dispatchEvent( "canplay" ); + + impl.readyState = self.HAVE_ENOUGH_DATA; + self.dispatchEvent( "canplaythrough" ); + + while( playerReadyCallbacks.length ) { + callback = playerReadyCallbacks.shift(); + callback(); + } + + // Auto-start if necessary + if( impl.autoplay ) { + self.play(); + } + } + + function getDuration() { + return player ? player.duration : NaN; + } + + function destroyPlayer() { + if( !( playerReady && player ) ) { + return; + } + player.pause(); + player = null; + parent.removeChild( elem ); + elem = document.createElement( "div" ); + } + + function changeSrc( aSrc ) { + if( !self._canPlaySrc( aSrc ) ) { + impl.error = { + name: "MediaError", + message: "Media Source Not Supported", + code: MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + }; + self.dispatchEvent( "error" ); + return; + } + + impl.src = aSrc; + + if( playerReady ) { + destroyPlayer(); + } + + elem.width = impl.width; + elem.height = impl.height; + parent.appendChild( elem ); + + // Parse out the start and duration, if specified + var fragments = temporalRegex.exec( aSrc ), + start = +fragments[ 1 ], + duration = +fragments[ 2 ]; + + player = new NullPlayer({ + currentTime: start, + duration: duration, + endedCallback: onEnded + }); + + self.dispatchEvent( "loadstart" ); + self.dispatchEvent( "progress" ); + self.dispatchEvent( "durationchange" ); + onPlayerReady(); + } + + function getCurrentTime() { + if( !playerReady ) { + return 0; + } + + return player.currentTime; + } + + function changeCurrentTime( aTime ) { + if ( aTime === getCurrentTime() ) { + return; + } + if( !playerReady ) { + addPlayerReadyCallback( function() { changeCurrentTime( aTime ); } ); + return; + } + + onSeeking(); + player.seekTo( aTime ); + onSeeked(); + } + + function onTimeUpdate() { + self.dispatchEvent( "timeupdate" ); + } + + function onSeeking( target ) { + impl.seeking = true; + self.dispatchEvent( "seeking" ); + } + + function onSeeked() { + impl.ended = false; + impl.seeking = false; + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "seeked" ); + self.dispatchEvent( "canplay" ); + self.dispatchEvent( "canplaythrough" ); + } + + function onPlay() { + // Deal with first time play vs. subsequent. + if( impl.paused === 1 ) { + impl.paused = false; + self.dispatchEvent( "play" ); + self.dispatchEvent( "playing" ); + } else { + if( impl.ended ) { + changeCurrentTime( 0 ); + impl.ended = false; + } + + if ( impl.paused ) { + impl.paused = false; + if ( !impl.loop ) { + self.dispatchEvent( "play" ); + } + self.dispatchEvent( "playing" ); + } + } + + timeUpdateInterval = setInterval( onTimeUpdate, + self._util.TIMEUPDATE_MS ); + } + + self.play = function() { + if( !playerReady ) { + addPlayerReadyCallback( function() { self.play(); } ); + return; + } + player.play(); + if ( impl.paused ) { + onPlay(); + } + }; + + function onPause() { + impl.paused = true; + clearInterval( timeUpdateInterval ); + self.dispatchEvent( "pause" ); + } + + self.pause = function() { + if( !playerReady ) { + addPlayerReadyCallback( function() { self.pause(); } ); + return; + } + player.pause(); + if ( !impl.paused ) { + onPause(); + } + }; + + function onEnded() { + if( impl.loop ) { + changeCurrentTime( 0 ); + self.play(); + } else { + impl.ended = true; + onPause(); + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "ended" ); + } + } + + function setVolume( aValue ) { + impl.volume = aValue; + self.dispatchEvent( "volumechange" ); + } + + function getVolume() { + return impl.volume; + } + + function setMuted( aValue ) { + impl.muted = aValue; + self.dispatchEvent( "volumechange" ); + } + + function getMuted() { + return impl.muted; + } + + Object.defineProperties( self, { + + src: { + get: function() { + return impl.src; + }, + set: function( aSrc ) { + if( aSrc && aSrc !== impl.src ) { + changeSrc( aSrc ); + } + } + }, + + autoplay: { + get: function() { + return impl.autoplay; + }, + set: function( aValue ) { + impl.autoplay = self._util.isAttributeSet( aValue ); + } + }, + + loop: { + get: function() { + return impl.loop; + }, + set: function( aValue ) { + impl.loop = self._util.isAttributeSet( aValue ); + } + }, + + width: { + get: function() { + return elem.width; + }, + set: function( aValue ) { + elem.width = aValue; + impl.width = elem.width; + } + }, + + height: { + get: function() { + return elem.height; + }, + set: function( aValue ) { + elem.height = aValue; + impl.height = elem.height; + } + }, + + currentTime: { + get: function() { + return getCurrentTime(); + }, + set: function( aValue ) { + changeCurrentTime( aValue ); + } + }, + + duration: { + get: function() { + return getDuration(); + } + }, + + ended: { + get: function() { + return impl.ended; + } + }, + + paused: { + get: function() { + return impl.paused; + } + }, + + seeking: { + get: function() { + return impl.seeking; + } + }, + + readyState: { + get: function() { + return impl.readyState; + } + }, + + networkState: { + get: function() { + return impl.networkState; + } + }, + + volume: { + get: function() { + return getVolume(); + }, + set: function( aValue ) { + if( aValue < 0 || aValue > 1 ) { + throw "Volume value must be between 0.0 and 1.0"; + } + setVolume( aValue ); + } + }, + + muted: { + get: function() { + return getMuted(); + }, + set: function( aValue ) { + setMuted( self._util.isAttributeSet( aValue ) ); + } + }, + + playbackRate: { + get: function() { + return player.playbackRate; + }, + set: function( aValue ) { + player.playbackRate = aValue; + self.dispatchEvent( "ratechange" ); + } + }, + + error: { + get: function() { + return impl.error; + } + } + }); + + self._canPlaySrc = Popcorn.HTMLNullVideoElement._canPlaySrc; + self.canPlayType = Popcorn.HTMLNullVideoElement.canPlayType; + + return self; + } + + Popcorn.HTMLNullVideoElement = function( id ) { + return new HTMLNullVideoElement( id ); + }; + + // Helper for identifying URLs we know how to play. + Popcorn.HTMLNullVideoElement._canPlaySrc = function( url ) { + return ( temporalRegex ).test( url ) ? + "probably" : + EMPTY_STRING; + }; + + // We'll attempt to support a mime type of video/x-nullvideo + Popcorn.HTMLNullVideoElement.canPlayType = function( type ) { + return type === "video/x-nullvideo" ? "probably" : EMPTY_STRING; + }; + +}( Popcorn, document )); +(function( Popcorn, window, document ) { + + var + + CURRENT_TIME_MONITOR_MS = 16, + EMPTY_STRING = "", + + // Setup for SoundCloud API + scReady = false, + scLoaded = false, + scCallbacks = []; + + function isSoundCloudReady() { + // If the SoundCloud Widget API + JS SDK aren't loaded, do it now. + if( !scLoaded ) { + Popcorn.getScript( "https://w.soundcloud.com/player/api.js", function() { + Popcorn.getScript( "https://connect.soundcloud.com/sdk.js", function() { + scReady = true; + + // XXX: SoundCloud won't let us use real URLs with the API, + // so we have to lookup the track URL, requiring authentication. + SC.initialize({ + client_id: "PRaNFlda6Bhf5utPjUsptg" + }); + + var i = scCallbacks.length; + while( i-- ) { + scCallbacks[ i ](); + delete scCallbacks[ i ]; + } + }); + }); + scLoaded = true; + } + return scReady; + } + + function addSoundCloudCallback( callback ) { + scCallbacks.unshift( callback ); + } + + + function HTMLSoundCloudAudioElement( id ) { + + // SoundCloud API requires postMessage + if( !window.postMessage ) { + throw "ERROR: HTMLSoundCloudAudioElement requires window.postMessage"; + } + + var self = new Popcorn._MediaElementProto(), + parent = typeof id === "string" ? Popcorn.dom.find( id ) : id, + elem = document.createElement( "iframe" ), + impl = { + src: EMPTY_STRING, + networkState: self.NETWORK_EMPTY, + readyState: self.HAVE_NOTHING, + seeking: false, + autoplay: EMPTY_STRING, + preload: EMPTY_STRING, + controls: false, + loop: false, + poster: EMPTY_STRING, + // SC Volume values are 0-100, we remap to 0-1 in volume getter/setter + volume: 1, + muted: 0, + currentTime: 0, + duration: NaN, + ended: false, + paused: true, + width: parent.width|0 ? parent.width : self._util.MIN_WIDTH, + height: parent.height|0 ? parent.height : self._util.MIN_HEIGHT, + error: null + }, + playerReady = false, + playerPaused = true, + player, + playerReadyCallbacks = [], + timeUpdateInterval, + currentTimeInterval, + lastCurrentTime = 0; + + // Namespace all events we'll produce + self._eventNamespace = Popcorn.guid( "HTMLSoundCloudAudioElement::" ); + + self.parentNode = parent; + + // Mark this as SoundCloud + self._util.type = "SoundCloud"; + + function addPlayerReadyCallback( callback ) { + playerReadyCallbacks.unshift( callback ); + } + + // SoundCloud's widget fires its READY event too early for the audio + // to be used (i.e., the widget is setup, but not the audio decoder). + // To deal with this we have to wait on loadProgress to fire with a + // loadedProgress > 0. + function onLoaded() { + // Wire-up runtime listeners + player.bind( SC.Widget.Events.LOAD_PROGRESS, function( data ) { + onStateChange({ + type: "loadProgress", + // currentTime is in ms vs. s + data: data.currentPosition / 1000 + }); + }); + + player.bind( SC.Widget.Events.PLAY_PROGRESS, function( data ) { + onStateChange({ + type: "playProgress", + // currentTime is in ms vs. s + data: data.currentPosition / 1000 + }); + }); + + player.bind( SC.Widget.Events.PLAY, function( data ) { + onStateChange({ + type: "play" + }); + }); + + player.bind( SC.Widget.Events.PAUSE, function( data ) { + onStateChange({ + type: "pause" + }); + }); + + player.bind( SC.Widget.Events.SEEK, function( data ) { + player.getPosition( function( currentTimeInMS ) { + // Convert milliseconds to seconds. + var currentTimeInSeconds = currentTimeInMS / 1000; + if ( impl.seeking ) { + if ( Math.floor( currentTimeInSeconds ) !== Math.floor( impl.currentTime ) ) { + // Convert Seconds back to milliseconds. + player.seekTo( impl.currentTime * 1000 ); + } else { + onSeeked(); + } + return; + } + onStateChange({ + type: "seek", + data: currentTimeInSeconds + }); + }); + }); + + player.bind( SC.Widget.Events.FINISH, function() { + onStateChange({ + type: "finish" + }); + }); + + playerReady = true; + player.getDuration( updateDuration ); + } + + // When the player widget is ready, kick-off a play/pause + // in order to get the data loading. We'll wait on loadedProgress. + // It's possible for the loadProgress to take time after play(), so + // we don't call pause() right away, but wait on loadedProgress to be 1 + // before we do. + function onPlayerReady( data ) { + + // Turn down the volume and kick-off a play to force load + player.bind( SC.Widget.Events.PLAY_PROGRESS, function( data ) { + // Turn down the volume. + // Loading has to be kicked off before volume can be changed. + player.setVolume( 0 ); + // Wait for both flash and HTML5 to play something. + if( data.currentPosition > 0 ) { + player.unbind( SC.Widget.Events.PLAY_PROGRESS ); + + player.bind( SC.Widget.Events.PAUSE, function() { + player.unbind( SC.Widget.Events.PAUSE ); + + // Play/Pause cycle is done, restore volume and continue loading. + player.setVolume( 1 ); + player.bind( SC.Widget.Events.SEEK, function() { + player.unbind( SC.Widget.Events.SEEK ); + onLoaded(); + }); + // Re seek back to 0, then we're back to default, loaded, and ready to go. + player.seekTo( 0 ); + }); + player.pause(); + } + }); + player.play(); + } + + function updateDuration( newDuration ) { + // SoundCloud gives duration in ms vs. s + newDuration = newDuration / 1000; + + var oldDuration = impl.duration; + + if( oldDuration !== newDuration ) { + impl.duration = newDuration; + self.dispatchEvent( "durationchange" ); + + // Deal with first update of duration + if( isNaN( oldDuration ) ) { + impl.networkState = self.NETWORK_IDLE; + impl.readyState = self.HAVE_METADATA; + self.dispatchEvent( "loadedmetadata" ); + + self.dispatchEvent( "loadeddata" ); + + impl.readyState = self.HAVE_FUTURE_DATA; + self.dispatchEvent( "canplay" ); + + impl.readyState = self.HAVE_ENOUGH_DATA; + self.dispatchEvent( "canplaythrough" ); + + var i = playerReadyCallbacks.length; + while( i-- ) { + playerReadyCallbacks[ i ](); + delete playerReadyCallbacks[ i ]; + } + + // Auto-start if necessary + if( impl.paused && impl.autoplay ) { + self.play(); + } + } + } + } + + function getDuration() { + if( !playerReady ) { + // Queue a getDuration() call so we have correct duration info for loadedmetadata + addPlayerReadyCallback( function() { getDuration(); } ); + } + + player.getDuration( updateDuration ); + } + + function destroyPlayer() { + if( !( playerReady && player ) ) { + return; + } + clearInterval( currentTimeInterval ); + player.pause(); + + player.unbind( SC.Widget.Events.READY ); + player.unbind( SC.Widget.Events.LOAD_PROGRESS ); + player.unbind( SC.Widget.Events.PLAY_PROGRESS ); + player.unbind( SC.Widget.Events.PLAY ); + player.unbind( SC.Widget.Events.PAUSE ); + player.unbind( SC.Widget.Events.SEEK ); + player.unbind( SC.Widget.Events.FINISH ); + + parent.removeChild( elem ); + elem = document.createElement( "iframe" ); + } + + self.play = function() { + impl.paused = false; + if( !playerReady ) { + addPlayerReadyCallback( function() { self.play(); } ); + return; + } + if( impl.ended ) { + changeCurrentTime( 0 ); + } + player.play(); + }; + + function changeCurrentTime( aTime ) { + impl.currentTime = aTime; + + // Convert to ms + aTime = aTime * 1000; + + function seek() { + onSeeking(); + player.seekTo( aTime ); + } + + if( !playerReady ) { + addMediaReadyCallback( seek ); + return; + } + + seek(); + } + + function onSeeking() { + impl.seeking = true; + self.dispatchEvent( "seeking" ); + } + + function onSeeked() { + // XXX: make sure seeks don't hold us in the ended state + impl.ended = false; + impl.seeking = false; + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "seeked" ); + self.dispatchEvent( "canplay" ); + self.dispatchEvent( "canplaythrough" ); + } + + self.pause = function() { + impl.paused = true; + if( !playerReady ) { + addPlayerReadyCallback( function() { self.pause(); } ); + return; + } + player.pause(); + }; + + function onPause() { + impl.paused = true; + if( !playerPaused ) { + playerPaused = true; + clearInterval( timeUpdateInterval ); + self.dispatchEvent( "pause" ); + } + } + + function onTimeUpdate() { + self.dispatchEvent( "timeupdate" ); + } + + function onPlay() { + if ( !currentTimeInterval ) { + currentTimeInterval = setInterval( monitorCurrentTime, + CURRENT_TIME_MONITOR_MS ) ; + + // Only 1 play when video.loop=true + if ( impl.loop ) { + self.dispatchEvent( "play" ); + } + } + + timeUpdateInterval = setInterval( onTimeUpdate, + self._util.TIMEUPDATE_MS ); + + impl.paused = false; + + if ( playerPaused ) { + playerPaused = false; + + // Only 1 play when video.loop=true + if ( !impl.loop ) { + self.dispatchEvent( "play" ); + } + self.dispatchEvent( "playing" ); + } + } + + function onEnded() { + if( impl.loop ) { + changeCurrentTime( 0 ); + self.play(); + } else { + // XXX: SoundCloud doesn't manage end/paused state well. We have to + // simulate a pause or we leave the player in a state where it can't + // restart playing after ended. Also, the onPause callback won't get + // called when we do self.pause() here, so we manually set impl.paused + // to get the state right. + impl.ended = true; + self.pause(); + onPause(); + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "ended" ); + } + } + + function onCurrentTime( currentTime ) { + impl.currentTime = currentTime; + + if( currentTime !== lastCurrentTime ) { + self.dispatchEvent( "timeupdate" ); + } + + lastCurrentTime = currentTime; + } + + function onStateChange( event ) { + switch ( event.type ) { + case "loadProgress": + self.dispatchEvent( "progress" ); + break; + case "playProgress": + onCurrentTime( event.data ); + break; + case "play": + onPlay(); + break; + case "pause": + onPause(); + break; + case "finish": + onEnded(); + break; + case "seek": + onCurrentTime( event.data ); + break; + } + } + + function monitorCurrentTime() { + if ( impl.ended ) { + return; + } + player.getPosition( function( currentTimeInMS ) { + // Convert from ms to s + onCurrentTime( currentTimeInMS / 1000 ); + }); + } + + function changeSrc( aSrc ) { + if( !self._canPlaySrc( aSrc ) ) { + impl.error = { + name: "MediaError", + message: "Media Source Not Supported", + code: MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + }; + self.dispatchEvent( "error" ); + return; + } + + impl.src = aSrc; + + if( playerReady ) { + destroyPlayer(); + } + + // Make sure SoundCloud is ready, and if not, register a callback + if( !isSoundCloudReady() ) { + addSoundCloudCallback( function() { changeSrc( aSrc ); } ); + return; + } + + playerReady = false; + + SC.get( "/resolve", { url: aSrc }, function( data ) { + var err; + if ( data.errors ) { + err = { name: "MediaError" }; + // Not sure why this is in an array, and how multiple errors should be handled. + // For now, I'll just use the first. We just need something. + if ( data.errors[ 0 ] ) { + if ( data.errors[ 0 ].error_message === "404 - Not Found" ) { + err.message = "Video not found."; + err.code = MediaError.MEDIA_ERR_NETWORK; + } + } + impl.error = err; + self.dispatchEvent( "error" ); + } + elem.id = Popcorn.guid( "soundcloud-" ); + elem.width = impl.width; + elem.height = impl.height; + elem.frameBorder = 0; + elem.webkitAllowFullScreen = true; + elem.mozAllowFullScreen = true; + elem.allowFullScreen = true; + + // Apply the current controls state, since iframe wasn't ready yet. + setControls( impl.controls ); + + parent.appendChild( elem ); + + elem.onload = function() { + elem.onload = null; + + player = SC.Widget( elem ); + player.bind( SC.Widget.Events.READY, onPlayerReady ); + + impl.networkState = self.NETWORK_LOADING; + self.dispatchEvent( "loadstart" ); + self.dispatchEvent( "progress" ); + }; + elem.src = "https://w.soundcloud.com/player/?url=" + data.uri + + "&show_artwork=false" + + "&buying=false" + + "&liking=false" + + "&sharing=false" + + "&download=false" + + "&show_comments=false" + + "&show_user=false" + + "&single_active=false"; + }); + } + + function setVolume( aValue ) { + impl.volume = aValue; + + if( !playerReady ) { + addPlayerReadyCallback( function() { + setVolume( aValue ); + }); + return; + } + player.setVolume( aValue ); + self.dispatchEvent( "volumechange" ); + } + + function getVolume() { + // If we're muted, the volume is cached on impl.muted. + return impl.muted > 0 ? impl.muted : impl.volume; + } + + function setMuted( aMute ) { + if( !playerReady ) { + impl.muted = aMute ? 1 : 0; + addPlayerReadyCallback( function() { + setMuted( aMute ); + }); + return; + } + + // Move the existing volume onto muted to cache + // until we unmute, and set the volume to 0. + if( aMute ) { + impl.muted = impl.volume; + setVolume( 0 ); + } else { + impl.muted = 0; + setVolume( impl.muted ); + } + } + + function getMuted() { + return impl.muted > 0; + } + + function setControls( controls ) { + // Due to loading issues with hidden content, we have to be careful + // about how we hide the player when controls=false. Using opacity:0 + // will let the content load, but allow mouse events. When it's totally + // loaded we can visibility:hidden + position:absolute it. + if ( playerReady ) { + elem.style.position = "absolute"; + elem.style.visibility = controls ? "visible" : "hidden"; + } else { + elem.style.opacity = controls ? "1" : "0"; + // Try to stop mouse events over the iframe while loading. This won't + // work in current Opera or IE, but there's not much I can do + elem.style.pointerEvents = controls ? "auto" : "none"; + } + impl.controls = controls; + } + + Object.defineProperties( self, { + + src: { + get: function() { + return impl.src; + }, + set: function( aSrc ) { + if( aSrc && aSrc !== impl.src ) { + changeSrc( aSrc ); + } + } + }, + + autoplay: { + get: function() { + return impl.autoplay; + }, + set: function( aValue ) { + impl.autoplay = self._util.isAttributeSet( aValue ); + } + }, + + loop: { + get: function() { + return impl.loop; + }, + set: function( aValue ) { + impl.loop = self._util.isAttributeSet( aValue ); + } + }, + + width: { + get: function() { + return elem.width; + }, + set: function( aValue ) { + elem.width = aValue; + impl.width = elem.width; + } + }, + + height: { + get: function() { + return elem.height; + }, + set: function( aValue ) { + elem.height = aValue; + impl.height = elem.height; + } + }, + + currentTime: { + get: function() { + return impl.currentTime; + }, + set: function( aValue ) { + changeCurrentTime( aValue ); + } + }, + + duration: { + get: function() { + return impl.duration; + } + }, + + ended: { + get: function() { + return impl.ended; + } + }, + + paused: { + get: function() { + return impl.paused; + } + }, + + seeking: { + get: function() { + return impl.seeking; + } + }, + + readyState: { + get: function() { + return impl.readyState; + } + }, + + networkState: { + get: function() { + return impl.networkState; + } + }, + + volume: { + get: function() { + return getVolume(); + }, + set: function( aValue ) { + if( aValue < 0 || aValue > 1 ) { + throw "Volume value must be between 0.0 and 1.0"; + } + setVolume( aValue ); + } + }, + + muted: { + get: function() { + return getMuted(); + }, + set: function( aValue ) { + setMuted( self._util.isAttributeSet( aValue ) ); + } + }, + + error: { + get: function() { + return impl.error; + } + }, + + // Similar to HTML5 Audio Elements, with SoundCloud you can + // hide all visible UI for the player by setting controls=false. + controls: { + get: function() { + return impl.controls; + }, + set: function( aValue ) { + setControls( !!aValue ); + } + } + }); + + self._canPlaySrc = Popcorn.HTMLSoundCloudAudioElement._canPlaySrc; + self.canPlayType = Popcorn.HTMLSoundCloudAudioElement.canPlayType; + + return self; + } + + Popcorn.HTMLSoundCloudAudioElement = function( id ) { + return new HTMLSoundCloudAudioElement( id ); + }; + + // Helper for identifying URLs we know how to play. + Popcorn.HTMLSoundCloudAudioElement._canPlaySrc = function( url ) { + return (/(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/).test( url ) ? + "probably" : EMPTY_STRING; + }; + + // We'll attempt to support a mime type of audio/x-soundcloud + Popcorn.HTMLSoundCloudAudioElement.canPlayType = function( type ) { + return type === "audio/x-soundcloud" ? "probably" : EMPTY_STRING; + }; + +}( Popcorn, window, document )); +(function( Popcorn, window, document ) { + + var + + CURRENT_TIME_MONITOR_MS = 16, + EMPTY_STRING = "", + VIMEO_HOST = "https://player.vimeo.com"; + + // Utility wrapper around postMessage interface + function VimeoPlayer( vimeoIFrame ) { + var self = this, + url = vimeoIFrame.src.split('?')[0], + muted = 0; + + if( url.substr(0, 2) === '//' ) { + url = window.location.protocol + url; + } + + function sendMessage( method, params ) { + var data = JSON.stringify({ + method: method, + value: params + }); + + // The iframe has been destroyed, it just doesn't know it + if ( !vimeoIFrame.contentWindow ) { + return; + } + + vimeoIFrame.contentWindow.postMessage( data, url ); + } + + var methods = ( "play pause paused seekTo unload getCurrentTime getDuration " + + "getVideoEmbedCode getVideoHeight getVideoWidth getVideoUrl " + + "getColor setColor setLoop getVolume setVolume addEventListener" ).split(" "); + methods.forEach( function( method ) { + // All current methods take 0 or 1 args, always send arg0 + self[ method ] = function( arg0 ) { + sendMessage( method, arg0 ); + }; + }); + } + + + function HTMLVimeoVideoElement( id ) { + + // Vimeo iframe API requires postMessage + if( !window.postMessage ) { + throw "ERROR: HTMLVimeoVideoElement requires window.postMessage"; + } + + var self = new Popcorn._MediaElementProto(), + parent = typeof id === "string" ? Popcorn.dom.find( id ) : id, + elem = document.createElement( "iframe" ), + impl = { + src: EMPTY_STRING, + networkState: self.NETWORK_EMPTY, + readyState: self.HAVE_NOTHING, + seeking: false, + autoplay: EMPTY_STRING, + preload: EMPTY_STRING, + controls: false, + loop: false, + poster: EMPTY_STRING, + // Vimeo seems to use .77 as default + volume: 1, + // Vimeo has no concept of muted, store volume values + // such that muted===0 is unmuted, and muted>0 is muted. + muted: 0, + currentTime: 0, + duration: NaN, + ended: false, + paused: true, + error: null + }, + playerReady = false, + playerUID = Popcorn.guid(), + player, + playerPaused = true, + playerReadyCallbacks = [], + timeUpdateInterval, + currentTimeInterval, + lastCurrentTime = 0; + + // Namespace all events we'll produce + self._eventNamespace = Popcorn.guid( "HTMLVimeoVideoElement::" ); + + self.parentNode = parent; + + // Mark type as Vimeo + self._util.type = "Vimeo"; + + function addPlayerReadyCallback( callback ) { + playerReadyCallbacks.unshift( callback ); + } + + function onPlayerReady( event ) { + player.addEventListener( 'loadProgress' ); + player.addEventListener( 'playProgress' ); + player.addEventListener( 'play' ); + player.addEventListener( 'pause' ); + player.addEventListener( 'finish' ); + player.addEventListener( 'seek' ); + + player.getDuration(); + + impl.networkState = self.NETWORK_LOADING; + self.dispatchEvent( "loadstart" ); + self.dispatchEvent( "progress" ); + } + + function updateDuration( newDuration ) { + var oldDuration = impl.duration; + + if( oldDuration !== newDuration ) { + impl.duration = newDuration; + self.dispatchEvent( "durationchange" ); + + // Deal with first update of duration + if( isNaN( oldDuration ) ) { + impl.networkState = self.NETWORK_IDLE; + impl.readyState = self.HAVE_METADATA; + self.dispatchEvent( "loadedmetadata" ); + + self.dispatchEvent( "loadeddata" ); + + impl.readyState = self.HAVE_FUTURE_DATA; + self.dispatchEvent( "canplay" ); + + impl.readyState = self.HAVE_ENOUGH_DATA; + self.dispatchEvent( "canplaythrough" ); + // Auto-start if necessary + if( impl.autoplay ) { + self.play(); + } + + var i = playerReadyCallbacks.length; + while( i-- ) { + playerReadyCallbacks[ i ](); + delete playerReadyCallbacks[ i ]; + } + } + } + } + + function getDuration() { + if( !playerReady ) { + // Queue a getDuration() call so we have correct duration info for loadedmetadata + addPlayerReadyCallback( function() { getDuration(); } ); + } + + player.getDuration(); + } + + function destroyPlayer() { + if( !( playerReady && player ) ) { + return; + } + clearInterval( currentTimeInterval ); + player.pause(); + + window.removeEventListener( 'message', onStateChange, false ); + parent.removeChild( elem ); + elem = document.createElement( "iframe" ); + } + + self.play = function() { + impl.paused = false; + if( !playerReady ) { + addPlayerReadyCallback( function() { self.play(); } ); + return; + } + + player.play(); + }; + + function changeCurrentTime( aTime ) { + if( !playerReady ) { + addPlayerReadyCallback( function() { changeCurrentTime( aTime ); } ); + return; + } + + onSeeking(); + player.seekTo( aTime ); + } + + function onSeeking() { + impl.seeking = true; + self.dispatchEvent( "seeking" ); + } + + function onSeeked() { + impl.seeking = false; + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "seeked" ); + self.dispatchEvent( "canplay" ); + self.dispatchEvent( "canplaythrough" ); + } + + self.pause = function() { + impl.paused = true; + if( !playerReady ) { + addPlayerReadyCallback( function() { self.pause(); } ); + return; + } + + player.pause(); + }; + + function onPause() { + impl.paused = true; + if ( !playerPaused ) { + playerPaused = true; + clearInterval( timeUpdateInterval ); + self.dispatchEvent( "pause" ); + } + } + + function onTimeUpdate() { + self.dispatchEvent( "timeupdate" ); + } + + function onPlay() { + if( impl.ended ) { + changeCurrentTime( 0 ); + } + + if ( !currentTimeInterval ) { + currentTimeInterval = setInterval( monitorCurrentTime, + CURRENT_TIME_MONITOR_MS ) ; + + // Only 1 play when video.loop=true + if ( impl.loop ) { + self.dispatchEvent( "play" ); + } + } + + timeUpdateInterval = setInterval( onTimeUpdate, + self._util.TIMEUPDATE_MS ); + + impl.paused = false; + if( playerPaused ) { + playerPaused = false; + + // Only 1 play when video.loop=true + if ( !impl.loop ) { + self.dispatchEvent( "play" ); + } + self.dispatchEvent( "playing" ); + } + } + + function onEnded() { + if( impl.loop ) { + changeCurrentTime( 0 ); + self.play(); + } else { + impl.ended = true; + self.dispatchEvent( "ended" ); + } + } + + function onCurrentTime( aTime ) { + var currentTime = impl.currentTime = aTime; + + if( currentTime !== lastCurrentTime ) { + self.dispatchEvent( "timeupdate" ); + } + + lastCurrentTime = impl.currentTime; + } + + // We deal with the startup load messages differently than + // we will once the player is fully ready and loaded. + // When the player is "ready" it is playable, but not + // yet seekable. We need to force a play() to get data + // to download (mimic preload=auto), or seeks will fail. + function startupMessage( event ) { + if( event.origin !== VIMEO_HOST ) { + return; + } + + var data; + try { + data = JSON.parse( event.data ); + } catch ( ex ) { + console.warn( ex ); + } + + if ( data.player_id != playerUID ) { + return; + } + + switch ( data.event ) { + case "ready": + player = new VimeoPlayer( elem ); + player.addEventListener( "loadProgress" ); + player.addEventListener( "pause" ); + player.setVolume( 0 ); + player.play(); + break; + case "loadProgress": + var duration = parseFloat( data.data.duration ); + if( duration > 0 && !playerReady ) { + playerReady = true; + player.pause(); + } + break; + case "pause": + player.setVolume( 1 ); + // Switch message pump to use run-time message callback vs. startup + window.removeEventListener( "message", startupMessage, false ); + window.addEventListener( "message", onStateChange, false ); + onPlayerReady(); + break; + } + } + + function onStateChange( event ) { + if( event.origin !== VIMEO_HOST ) { + return; + } + + var data; + try { + data = JSON.parse( event.data ); + } catch ( ex ) { + console.warn( ex ); + } + + if ( data.player_id != playerUID ) { + return; + } + + // Methods + switch ( data.method ) { + case "getCurrentTime": + onCurrentTime( parseFloat( data.value ) ); + break; + case "getDuration": + updateDuration( parseFloat( data.value ) ); + break; + case "getVolume": + onVolume( parseFloat( data.value ) ); + break; + } + + // Events + switch ( data.event ) { + case "loadProgress": + self.dispatchEvent( "progress" ); + updateDuration( parseFloat( data.data.duration ) ); + break; + case "playProgress": + onCurrentTime( parseFloat( data.data.seconds ) ); + break; + case "play": + onPlay(); + break; + case "pause": + onPause(); + break; + case "finish": + onEnded(); + break; + case "seek": + onCurrentTime( parseFloat( data.data.seconds ) ); + onSeeked(); + break; + } + } + + function monitorCurrentTime() { + player.getCurrentTime(); + } + + function changeSrc( aSrc ) { + if( !self._canPlaySrc( aSrc ) ) { + impl.error = { + name: "MediaError", + message: "Media Source Not Supported", + code: MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + }; + self.dispatchEvent( "error" ); + return; + } + + impl.src = aSrc; + + if( playerReady ) { + destroyPlayer(); + } + + playerReady = false; + + var src = self._util.parseUri( aSrc ), + queryKey = src.queryKey, + key, + optionsArray = [ + // Vimeo API options first + "api=1", + "player_id=" + playerUID, + // Turn off as much of the metadata/branding as possible + "title=0", + "byline=0", + "portrait=0" + ]; + + // Sync loop and autoplay based on URL params, and delete. + // We'll manage both internally. + impl.loop = queryKey.loop === "1" || impl.loop; + delete queryKey.loop; + impl.autoplay = queryKey.autoplay === "1" || impl.autoplay; + delete queryKey.autoplay; + + // Create the base vimeo player string. It will always have query string options + src = VIMEO_HOST + '/video/' + ( /\d+$/ ).exec( src.path ) + "?"; + for( key in queryKey ) { + if ( queryKey.hasOwnProperty( key ) ) { + optionsArray.push( encodeURIComponent( key ) + "=" + + encodeURIComponent( queryKey[ key ] ) ); + } + } + src += optionsArray.join( "&" ); + + elem.id = playerUID; + elem.style.width = "100%"; + elem.style.height = "100%"; + elem.frameBorder = 0; + elem.webkitAllowFullScreen = true; + elem.mozAllowFullScreen = true; + elem.allowFullScreen = true; + parent.appendChild( elem ); + elem.src = src; + + window.addEventListener( "message", startupMessage, false ); + } + + function onVolume( aValue ) { + if( impl.volume !== aValue ) { + impl.volume = aValue; + self.dispatchEvent( "volumechange" ); + } + } + + function setVolume( aValue ) { + impl.volume = aValue; + + if( !playerReady ) { + addPlayerReadyCallback( function() { + setVolume( aValue ); + }); + return; + } + player.setVolume( aValue ); + self.dispatchEvent( "volumechange" ); + } + + function getVolume() { + // If we're muted, the volume is cached on impl.muted. + return impl.muted > 0 ? impl.muted : impl.volume; + } + + function setMuted( aMute ) { + if( !playerReady ) { + impl.muted = aMute ? 1 : 0; + addPlayerReadyCallback( function() { + setMuted( aMute ); + }); + return; + } + + // Move the existing volume onto muted to cache + // until we unmute, and set the volume to 0. + if( aMute ) { + impl.muted = impl.volume; + setVolume( 0 ); + } else { + impl.muted = 0; + setVolume( impl.muted ); + } + } + + function getMuted() { + return impl.muted > 0; + } + + Object.defineProperties( self, { + + src: { + get: function() { + return impl.src; + }, + set: function( aSrc ) { + if( aSrc && aSrc !== impl.src ) { + changeSrc( aSrc ); + } + } + }, + + autoplay: { + get: function() { + return impl.autoplay; + }, + set: function( aValue ) { + impl.autoplay = self._util.isAttributeSet( aValue ); + } + }, + + loop: { + get: function() { + return impl.loop; + }, + set: function( aValue ) { + impl.loop = self._util.isAttributeSet( aValue ); + } + }, + + width: { + get: function() { + return self.parentNode.offsetWidth; + } + }, + + height: { + get: function() { + return self.parentNode.offsetHeight; + } + }, + + currentTime: { + get: function() { + return impl.currentTime; + }, + set: function( aValue ) { + changeCurrentTime( aValue ); + } + }, + + duration: { + get: function() { + return impl.duration; + } + }, + + ended: { + get: function() { + return impl.ended; + } + }, + + paused: { + get: function() { + return impl.paused; + } + }, + + seeking: { + get: function() { + return impl.seeking; + } + }, + + readyState: { + get: function() { + return impl.readyState; + } + }, + + networkState: { + get: function() { + return impl.networkState; + } + }, + + volume: { + get: function() { + return getVolume(); + }, + set: function( aValue ) { + if( aValue < 0 || aValue > 1 ) { + throw "Volume value must be between 0.0 and 1.0"; + } + + setVolume( aValue ); + } + }, + + muted: { + get: function() { + return getMuted(); + }, + set: function( aValue ) { + setMuted( self._util.isAttributeSet( aValue ) ); + } + }, + + error: { + get: function() { + return impl.error; + } + } + }); + + self._canPlaySrc = Popcorn.HTMLVimeoVideoElement._canPlaySrc; + self.canPlayType = Popcorn.HTMLVimeoVideoElement.canPlayType; + + return self; + } + + Popcorn.HTMLVimeoVideoElement = function( id ) { + return new HTMLVimeoVideoElement( id ); + }; + + // Helper for identifying URLs we know how to play. + Popcorn.HTMLVimeoVideoElement._canPlaySrc = function( url ) { + return ( (/player.vimeo.com\/video\/\d+/).test( url ) || + (/vimeo.com\/\d+/).test( url ) ) ? "probably" : EMPTY_STRING; + }; + + // We'll attempt to support a mime type of video/x-vimeo + Popcorn.HTMLVimeoVideoElement.canPlayType = function( type ) { + return type === "video/x-vimeo" ? "probably" : EMPTY_STRING; + }; + +}( Popcorn, window, document )); +(function( Popcorn, window, document ) { + + var + + CURRENT_TIME_MONITOR_MS = 10, + EMPTY_STRING = "", + + // Example: http://www.youtube.com/watch?v=12345678901 + regexYouTube = /^.*(?:\/|v=)(.{11})/, + + ABS = Math.abs, + + // Setup for YouTube API + ytReady = false, + ytLoading = false, + ytCallbacks = []; + + function onYouTubeIframeAPIReady() { + var callback; + if ( YT.loaded ) { + ytReady = true; + while( ytCallbacks.length ) { + callback = ytCallbacks.shift(); + callback(); + } + } else { + setTimeout( onYouTubeIframeAPIReady, 250 ); + } + } + + function isYouTubeReady() { + var script; + // If we area already waiting, do nothing. + if( !ytLoading ) { + // If script is already there, check if it is loaded. + if ( window.YT ) { + onYouTubeIframeAPIReady(); + } else { + script = document.createElement( "script" ); + script.addEventListener( "load", onYouTubeIframeAPIReady, false); + script.src = "https://www.youtube.com/iframe_api"; + document.head.appendChild( script ); + } + ytLoading = true; + } + return ytReady; + } + + function addYouTubeCallback( callback ) { + ytCallbacks.push( callback ); + } + + function HTMLYouTubeVideoElement( id ) { + + // YouTube iframe API requires postMessage + if( !window.postMessage ) { + throw "ERROR: HTMLYouTubeVideoElement requires window.postMessage"; + } + + var self = new Popcorn._MediaElementProto(), + parent = typeof id === "string" ? document.querySelector( id ) : id, + elem = document.createElement( "div" ), + impl = { + src: EMPTY_STRING, + networkState: self.NETWORK_EMPTY, + readyState: self.HAVE_NOTHING, + seeking: false, + autoplay: EMPTY_STRING, + preload: EMPTY_STRING, + controls: false, + loop: false, + poster: EMPTY_STRING, + volume: 1, + muted: false, + currentTime: 0, + duration: NaN, + ended: false, + paused: true, + error: null + }, + playerReady = false, + mediaReady = false, + loopedPlay = false, + player, + playerPaused = true, + mediaReadyCallbacks = [], + playerState = -1, + bufferedInterval, + lastLoadedFraction = 0, + currentTimeInterval, + timeUpdateInterval; + + // Namespace all events we'll produce + self._eventNamespace = Popcorn.guid( "HTMLYouTubeVideoElement::" ); + + self.parentNode = parent; + + // Mark this as YouTube + self._util.type = "YouTube"; + + function addMediaReadyCallback( callback ) { + mediaReadyCallbacks.push( callback ); + } + + function catchRoguePlayEvent() { + player.pauseVideo(); + removeYouTubeEvent( "play", catchRoguePlayEvent ); + addYouTubeEvent( "play", onPlay ); + } + + function catchRoguePauseEvent() { + addYouTubeEvent( "pause", onPause ); + removeYouTubeEvent( "pause", catchRoguePauseEvent ); + } + + function onPlayerReady( event ) { + + var onMuted = function() { + if ( player.isMuted() ) { + // force an initial play on the video, to remove autostart on initial seekTo. + addYouTubeEvent( "play", onFirstPlay ); + player.playVideo(); + } else { + setTimeout( onMuted, 0 ); + } + }; + playerReady = true; + // XXX: this should really live in cued below, but doesn't work. + + // Browsers using flash will have the pause() call take too long and cause some + // sound to leak out. Muting before to prevent this. + player.mute(); + + // ensure we are muted. + onMuted(); + } + + function onPlayerError(event) { + // There's no perfect mapping to HTML5 errors from YouTube errors. + var err = { name: "MediaError" }; + + switch( event.data ) { + + // invalid parameter + case 2: + err.message = "Invalid video parameter."; + err.code = MediaError.MEDIA_ERR_ABORTED; + break; + + // HTML5 Error + case 5: + err.message = "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred."; + err.code = MediaError.MEDIA_ERR_DECODE; + + // requested video not found + case 100: + err.message = "Video not found."; + err.code = MediaError.MEDIA_ERR_NETWORK; + break; + + // video can't be embedded by request of owner + case 101: + case 150: + err.message = "Video not usable."; + err.code = MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED; + break; + + default: + err.message = "Unknown error."; + err.code = 5; + } + + impl.error = err; + self.dispatchEvent( "error" ); + } + + function onReady() { + + addYouTubeEvent( "play", onPlay ); + addYouTubeEvent( "pause", onPause ); + // Set initial paused state + if( impl.autoplay || !impl.paused ) { + removeYouTubeEvent( "play", onReady ); + impl.paused = false; + addMediaReadyCallback(function() { + if ( !impl.paused ) { + onPlay(); + } + }); + } + + // Ensure video will now be unmuted when playing due to the mute on initial load. + if( !impl.muted ) { + player.unMute(); + } + + impl.readyState = self.HAVE_METADATA; + self.dispatchEvent( "loadedmetadata" ); + currentTimeInterval = setInterval( monitorCurrentTime, + CURRENT_TIME_MONITOR_MS ); + + self.dispatchEvent( "loadeddata" ); + + impl.readyState = self.HAVE_FUTURE_DATA; + self.dispatchEvent( "canplay" ); + + mediaReady = true; + bufferedInterval = setInterval( monitorBuffered, 50 ); + + while( mediaReadyCallbacks.length ) { + mediaReadyCallbacks[ 0 ](); + mediaReadyCallbacks.shift(); + } + + // We can't easily determine canplaythrough, but will send anyway. + impl.readyState = self.HAVE_ENOUGH_DATA; + self.dispatchEvent( "canplaythrough" ); + } + + function onFirstPause() { + removeYouTubeEvent( "pause", onFirstPause ); + if ( player.getCurrentTime() > 0 ) { + setTimeout( onFirstPause, 0 ); + return; + } + + if( impl.autoplay || !impl.paused ) { + addYouTubeEvent( "play", onReady ); + player.playVideo(); + } else { + onReady(); + } + } + + // This function needs duration and first play to be ready. + function onFirstPlay() { + removeYouTubeEvent( "play", onFirstPlay ); + if ( player.getCurrentTime() === 0 ) { + setTimeout( onFirstPlay, 0 ); + return; + } + addYouTubeEvent( "pause", onFirstPause ); + player.seekTo( 0 ); + player.pauseVideo(); + } + + function addYouTubeEvent( event, listener ) { + self.addEventListener( "youtube-" + event, listener, false ); + } + function removeYouTubeEvent( event, listener ) { + self.removeEventListener( "youtube-" + event, listener, false ); + } + function dispatchYouTubeEvent( event ) { + self.dispatchEvent( "youtube-" + event ); + } + + function onBuffering() { + impl.networkState = self.NETWORK_LOADING; + var newDuration = player.getDuration(); + if (impl.duration !== newDuration) { + impl.duration = newDuration; + self.dispatchEvent( "durationchange" ); + } + self.dispatchEvent( "waiting" ); + } + + addYouTubeEvent( "buffering", onBuffering ); + addYouTubeEvent( "ended", onEnded ); + + function onPlayerStateChange( event ) { + + switch( event.data ) { + + // ended + case YT.PlayerState.ENDED: + dispatchYouTubeEvent( "ended" ); + break; + + // playing + case YT.PlayerState.PLAYING: + dispatchYouTubeEvent( "play" ); + break; + + // paused + case YT.PlayerState.PAUSED: + // Youtube fires a paused event before an ended event. + // We have no need for this. + if ( player.getDuration() !== player.getCurrentTime() ) { + dispatchYouTubeEvent( "pause" ); + } + break; + + // buffering + case YT.PlayerState.BUFFERING: + dispatchYouTubeEvent( "buffering" ); + break; + + // video cued + case YT.PlayerState.CUED: + // XXX: cued doesn't seem to fire reliably, bug in youtube api? + break; + } + + if ( event.data !== YT.PlayerState.BUFFERING && + playerState === YT.PlayerState.BUFFERING ) { + onProgress(); + } + + playerState = event.data; + } + + function destroyPlayer() { + if( !( playerReady && player ) ) { + return; + } + + removeYouTubeEvent( "buffering", onBuffering ); + removeYouTubeEvent( "ended", onEnded ); + removeYouTubeEvent( "play", onPlay ); + removeYouTubeEvent( "pause", onPause ); + onPause(); + mediaReady = false; + loopedPlay = false; + impl.currentTime = 0; + mediaReadyCallbacks = []; + clearInterval( currentTimeInterval ); + clearInterval( bufferedInterval ); + player.stopVideo(); + player.clearVideo(); + player.destroy(); + elem = document.createElement( "div" ); + } + + function changeSrc( aSrc ) { + if( !self._canPlaySrc( aSrc ) ) { + impl.error = { + name: "MediaError", + message: "Media Source Not Supported", + code: MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + }; + self.dispatchEvent( "error" ); + return; + } + + impl.src = aSrc; + + // Make sure YouTube is ready, and if not, register a callback + if( !isYouTubeReady() ) { + addYouTubeCallback( function() { changeSrc( aSrc ); } ); + return; + } + + if( playerReady ) { + if( mediaReady ) { + destroyPlayer(); + } else { + addMediaReadyCallback( function() { + changeSrc( aSrc ); + }); + return; + } + } + + parent.appendChild( elem ); + + // Use any player vars passed on the URL + var playerVars = self._util.parseUri( aSrc ).queryKey; + + // Remove the video id, since we don't want to pass it + delete playerVars.v; + + // Sync autoplay, but manage internally + impl.autoplay = playerVars.autoplay === "1" || impl.autoplay; + delete playerVars.autoplay; + + // Sync loop, but manage internally + impl.loop = playerVars.loop === "1" || impl.loop; + delete playerVars.loop; + + // Don't show related videos when ending + playerVars.rel = playerVars.rel || 0; + + // Don't show YouTube's branding + playerVars.modestbranding = playerVars.modestbranding || 1; + + // Don't show annotations by default + playerVars.iv_load_policy = playerVars.iv_load_policy || 3; + + // Disable keyboard controls by default + playerVars.disablekb = playerVars.disablekb || 1; + + // Don't show video info before playing + playerVars.showinfo = playerVars.showinfo || 0; + + // Specify our domain as origin for iframe security + var domain = window.location.protocol === "file:" ? "*" : + window.location.protocol + "//" + window.location.host; + playerVars.origin = playerVars.origin || domain; + + // Show/hide controls. Sync with impl.controls and prefer URL value. + playerVars.controls = playerVars.controls || impl.controls ? 2 : 0; + impl.controls = playerVars.controls; + + // Set wmode to transparent to show video overlays + playerVars.wmode = playerVars.wmode || "opaque"; + + if ( playerVars.html5 !== 0 ) { + playerVars.html5 = 1; + } + + // Get video ID out of youtube url + aSrc = regexYouTube.exec( aSrc )[ 1 ]; + + player = new YT.Player( elem, { + width: "100%", + height: "100%", + wmode: playerVars.wmode, + videoId: aSrc, + playerVars: playerVars, + events: { + 'onReady': onPlayerReady, + 'onError': onPlayerError, + 'onStateChange': onPlayerStateChange + } + }); + + impl.networkState = self.NETWORK_LOADING; + self.dispatchEvent( "loadstart" ); + self.dispatchEvent( "progress" ); + } + + function monitorCurrentTime() { + var playerTime = player.getCurrentTime(); + if ( !impl.seeking ) { + if ( ABS( impl.currentTime - playerTime ) > CURRENT_TIME_MONITOR_MS ) { + onSeeking(); + onSeeked(); + } + impl.currentTime = playerTime; + } else if ( ABS( playerTime - impl.currentTime ) < 1 ) { + onSeeked(); + } + } + + function monitorBuffered() { + var fraction = player.getVideoLoadedFraction(); + + if ( fraction && lastLoadedFraction !== fraction ) { + lastLoadedFraction = fraction; + onProgress(); + } + } + + function changeCurrentTime( aTime ) { + if ( aTime === impl.currentTime ) { + return; + } + impl.currentTime = aTime; + if( !mediaReady ) { + addMediaReadyCallback( function() { + + onSeeking(); + player.seekTo( aTime ); + }); + return; + } + + onSeeking(); + player.seekTo( aTime ); + } + + function onTimeUpdate() { + self.dispatchEvent( "timeupdate" ); + } + + function onSeeking() { + // a seek in youtube fires a paused event. + // we don't want to listen for this, so this state catches the event. + addYouTubeEvent( "pause", catchRoguePauseEvent ); + removeYouTubeEvent( "pause", onPause ); + impl.seeking = true; + self.dispatchEvent( "seeking" ); + } + + function onSeeked() { + impl.ended = false; + impl.seeking = false; + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "seeked" ); + self.dispatchEvent( "canplay" ); + self.dispatchEvent( "canplaythrough" ); + } + + function onPlay() { + if( impl.ended ) { + changeCurrentTime( 0 ); + impl.ended = false; + } + timeUpdateInterval = setInterval( onTimeUpdate, + self._util.TIMEUPDATE_MS ); + impl.paused = false; + if( playerPaused ) { + playerPaused = false; + + // Only 1 play when video.loop=true + if ( ( impl.loop && !loopedPlay ) || !impl.loop ) { + loopedPlay = true; + self.dispatchEvent( "play" ); + } + self.dispatchEvent( "playing" ); + } + } + + function onProgress() { + self.dispatchEvent( "progress" ); + } + + self.play = function() { + impl.paused = false; + if( !mediaReady ) { + addMediaReadyCallback( function() { + self.play(); + }); + return; + } + player.playVideo(); + }; + + function onPause() { + impl.paused = true; + if ( !playerPaused ) { + playerPaused = true; + clearInterval( timeUpdateInterval ); + self.dispatchEvent( "pause" ); + } + } + + self.pause = function() { + impl.paused = true; + if( !mediaReady ) { + addMediaReadyCallback( function() { + self.pause(); + }); + return; + } + // if a pause happens while seeking, ensure we catch it. + // in youtube seeks fire pause events, and we don't want to listen to that. + // except for the case of an actual pause. + catchRoguePauseEvent(); + player.pauseVideo(); + }; + + function onEnded() { + if( impl.loop ) { + changeCurrentTime( 0 ); + self.play(); + } else { + impl.ended = true; + onPause(); + // YouTube will fire a Playing State change after the video has ended, causing it to loop. + addYouTubeEvent( "play", catchRoguePlayEvent ); + removeYouTubeEvent( "play", onPlay ); + self.dispatchEvent( "timeupdate" ); + self.dispatchEvent( "ended" ); + } + } + + function setMuted( aValue ) { + impl.muted = aValue; + if( !mediaReady ) { + addMediaReadyCallback( function() { + setMuted( impl.muted ); + }); + return; + } + player[ aValue ? "mute" : "unMute" ](); + self.dispatchEvent( "volumechange" ); + } + + function getMuted() { + // YouTube has isMuted(), but for sync access we use impl.muted + return impl.muted; + } + + Object.defineProperties( self, { + + src: { + get: function() { + return impl.src; + }, + set: function( aSrc ) { + if( aSrc && aSrc !== impl.src ) { + changeSrc( aSrc ); + } + } + }, + + autoplay: { + get: function() { + return impl.autoplay; + }, + set: function( aValue ) { + impl.autoplay = self._util.isAttributeSet( aValue ); + } + }, + + loop: { + get: function() { + return impl.loop; + }, + set: function( aValue ) { + impl.loop = self._util.isAttributeSet( aValue ); + } + }, + + width: { + get: function() { + return self.parentNode.offsetWidth; + } + }, + + height: { + get: function() { + return self.parentNode.offsetHeight; + } + }, + + currentTime: { + get: function() { + return impl.currentTime; + }, + set: function( aValue ) { + changeCurrentTime( aValue ); + } + }, + + duration: { + get: function() { + return impl.duration; + } + }, + + ended: { + get: function() { + return impl.ended; + } + }, + + paused: { + get: function() { + return impl.paused; + } + }, + + seeking: { + get: function() { + return impl.seeking; + } + }, + + readyState: { + get: function() { + return impl.readyState; + } + }, + + networkState: { + get: function() { + return impl.networkState; + } + }, + + volume: { + get: function() { + return impl.volume; + }, + set: function( aValue ) { + if( aValue < 0 || aValue > 1 ) { + throw "Volume value must be between 0.0 and 1.0"; + } + impl.volume = aValue; + if( !mediaReady ) { + addMediaReadyCallback( function() { + self.volume = aValue; + }); + return; + } + player.setVolume( impl.volume * 100 ); + self.dispatchEvent( "volumechange" ); + } + }, + + muted: { + get: function() { + return getMuted(); + }, + set: function( aValue ) { + setMuted( self._util.isAttributeSet( aValue ) ); + } + }, + + error: { + get: function() { + return impl.error; + } + }, + + buffered: { + get: function () { + var timeRanges = { + start: function( index ) { + if ( index === 0 ) { + return 0; + } + + //throw fake DOMException/INDEX_SIZE_ERR + throw "INDEX_SIZE_ERR: DOM Exception 1"; + }, + end: function( index ) { + if ( index === 0 ) { + if ( !impl.duration ) { + return 0; + } + + return impl.duration * lastLoadedFraction; + } + + //throw fake DOMException/INDEX_SIZE_ERR + throw "INDEX_SIZE_ERR: DOM Exception 1"; + }, + length: 1 + }; + + return timeRanges; + }, + configurable: true + } + }); + + self._canPlaySrc = Popcorn.HTMLYouTubeVideoElement._canPlaySrc; + self.canPlayType = Popcorn.HTMLYouTubeVideoElement.canPlayType; + + return self; + } + + Popcorn.HTMLYouTubeVideoElement = function( id ) { + return new HTMLYouTubeVideoElement( id ); + }; + + // Helper for identifying URLs we know how to play. + Popcorn.HTMLYouTubeVideoElement._canPlaySrc = function( url ) { + return (/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu).*(?:\/|v=)(.{11})/).test( url ) ? + "probably" : + EMPTY_STRING; + }; + + // We'll attempt to support a mime type of video/x-youtube + Popcorn.HTMLYouTubeVideoElement.canPlayType = function( type ) { + return type === "video/x-youtube" ? "probably" : EMPTY_STRING; + }; + +}( Popcorn, window, document )); diff --git a/dist/popcorn.wrappers.min.js b/dist/popcorn.wrappers.min.js new file mode 100644 index 000000000..1d2932fbc --- /dev/null +++ b/dist/popcorn.wrappers.min.js @@ -0,0 +1,62 @@ +(function(n,A){function D(p){var E=D.options;p=E.parser[E.strictMode?"strict":"loose"].exec(p);for(var e={},u=14;u--;)e[E.key[u]]=p[u]||"";e[E.q.name]={};e[E.key[12]].replace(E.q.parser,function(v,K,I){if(K)e[E.q.name][K]=I});return e}D.options={strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, +loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};var O={length:0,start:n.nop,end:n.nop};window.MediaError=window.MediaError||function(){function p(E,e){this.code=E||null;this.message=e||""}p.MEDIA_ERR_NONE_ACTIVE=0;p.MEDIA_ERR_ABORTED=1;p.MEDIA_ERR_NETWORK=2;p.MEDIA_ERR_DECODE=3;p.MEDIA_ERR_NONE_SUPPORTED=4;return p}();n._MediaElementProto=function(){var p= +{},E;Object.prototype.__defineGetter__||(p=A.createElement("div"));p._util={type:"HTML5",TIMEUPDATE_MS:250,MIN_WIDTH:300,MIN_HEIGHT:150,isAttributeSet:function(e){return typeof e==="string"||e===true},parseUri:D};p.addEventListener=function(e,u,v){A.addEventListener(this._eventNamespace+e,u,v)};p.removeEventListener=function(e,u,v){A.removeEventListener(this._eventNamespace+e,u,v)};p.dispatchEvent=function(e){var u=A.createEvent("CustomEvent");u.initCustomEvent(this._eventNamespace+e,false,false, +{type:e,target:this.parentNode,data:null});A.dispatchEvent(u)};p.load=n.nop;p.canPlayType=function(){return""};p.getBoundingClientRect=function(){return E.getBoundingClientRect()};p.NETWORK_EMPTY=0;p.NETWORK_IDLE=1;p.NETWORK_LOADING=2;p.NETWORK_NO_SOURCE=3;p.HAVE_NOTHING=0;p.HAVE_METADATA=1;p.HAVE_CURRENT_DATA=2;p.HAVE_FUTURE_DATA=3;p.HAVE_ENOUGH_DATA=4;Object.defineProperties(p,{currentSrc:{get:function(){return this.src!==undefined?this.src:""},configurable:true},parentNode:{get:function(){return E}, +set:function(e){E=e},configurable:true},preload:{get:function(){return"auto"},set:n.nop,configurable:true},controls:{get:function(){return true},set:n.nop,configurable:true},poster:{get:function(){return""},set:n.nop,configurable:true},crossorigin:{get:function(){return""},configurable:true},played:{get:function(){return O},configurable:true},seekable:{get:function(){return O},configurable:true},buffered:{get:function(){return O},configurable:true},defaultMuted:{get:function(){return false},configurable:true}, +defaultPlaybackRate:{get:function(){return 1},configurable:true},style:{get:function(){return this.parentNode.style},configurable:true},id:{get:function(){return this.parentNode.id},configurable:true}});return p}})(Popcorn,window.document);(function(n,A){function D(){return"maybe"}function O(p,E){var e=typeof p==="string"?A.querySelector(p):p,u=A.createElement(E);e.appendChild(u);u._canPlaySrc=D;return u}n.HTMLVideoElement=function(p){return O(p,"video")};n.HTMLVideoElement._canPlaySrc=D;n.HTMLAudioElement=function(p){return O(p,"audio")};n.HTMLAudioElement._canPlaySrc=D})(Popcorn,window.document);(function(n,A,D){function O(){if(A.jwplayer){v=true;for(var s=I.length;s--;){I[s]();delete I[s]}}else setTimeout(O,100)}function p(){if(!K){if(!A.jwplayer){var s=D.createElement("script");s.src="https://jwpsrv.com/library/zaIF4JI9EeK2FSIACpYGxA.js";var H=D.getElementsByTagName("script")[0];H.parentNode.insertBefore(s,H)}K=true;O()}return v}function E(s){I.unshift(s)}function e(s){function H(r){b.unshift(r)}function j(){var r=w.getDuration();if(r==-1||r==undefined)setTimeout(j,0);else{a.duration=r; +k.dispatchEvent("durationchange");M=true;a.readyState=k.HAVE_METADATA;k.dispatchEvent("loadedmetadata");k.dispatchEvent("loadeddata");a.readyState=k.HAVE_FUTURE_DATA;k.dispatchEvent("canplay");for(J=true;b.length;){b[0]();b.shift()}a.readyState=k.HAVE_ENOUGH_DATA;k.dispatchEvent("canplaythrough")}}function y(){if(f)f=false;else if(g){g=false;j()}else B()}function F(){if(a.seeking){a.ended=false;a.seeking=false;k.dispatchEvent("timeupdate");k.dispatchEvent("seeked");k.dispatchEvent("canplay");k.dispatchEvent("canplaythrough")}} +function P(){w.onPause(y);w.onTime(function(){if(!a.ended&&!a.seeking){a.currentTime=w.getPosition();k.dispatchEvent("timeupdate")}});w.onSeek(F);w.onPlay(function(){if(!a.ended)if(Q){Q=false;if(a.autoplay||!a.paused){a.paused=false;H(o);j()}else{c=g=true;w.pause(true)}}else if(c){c=false;f=true;w.pause(true)}else o()});w.onBufferChange(m);w.onComplete(L);w.play(true)}function C(r){var R={name:"MediaError"};R.message=r.message;R.code=r.code||5;a.error=R;k.dispatchEvent("error")}function q(r){if(k._canPlaySrc(r)){var R= +k._util.parseUri(r).queryKey;a.controls=R.controls=R.controls||a.controls;a.src=r;if(p()){if(M)M&&w&&w.destroy();R={width:"100%",height:"100%",autostart:a.autoplay,controls:a.controls};if(typeof r=="string")R.file=r;else R.sources=r;jwplayer(x.id).setup(R);w=jwplayer(x.id);w.onReady(P);w.onError(C);jwplayer.utils.log=function(d,V){if(typeof console!=="undefined"&&typeof console.log!=="undefined")V?console.log(d,V):console.log(d);d==="No suitable players found and fallback enabled"&&C({message:d,code:4})}; +a.networkState=k.NETWORK_LOADING;k.dispatchEvent("loadstart");k.dispatchEvent("progress")}else E(function(){q(r)})}else{a.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};k.dispatchEvent("error")}}function h(r){a.currentTime=r;if(J){N();w.seek(r)}else H(function(){N();w.seek(r)})}function N(){a.seeking=true;if(a.paused)c=true;k.dispatchEvent("seeking")}function o(){a.paused=false;if(M&&S){S=false;if(a.loop&&!T||!a.loop){T=true;k.dispatchEvent("play")}k.dispatchEvent("playing")}} +function m(){k.dispatchEvent("progress")}function B(){a.paused=true;if(!S){S=true;k.dispatchEvent("pause")}}function L(){if(a.loop)h(0);else{a.ended=true;B();k.dispatchEvent("timeupdate");k.dispatchEvent("ended")}}function z(r){a.volume=r;if(J){w.setVolume(a.volume*100);k.dispatchEvent("volumechange")}else H(function(){z(a.volume)})}function l(r){a.muted=r;if(J){w.setMute(r);k.dispatchEvent("volumechange")}else H(function(){l(a.muted)})}if(!A.postMessage)throw"ERROR: HTMLJWPlayerVideoElement requires window.postMessage"; +var k=new n._MediaElementProto,x=typeof s==="string"?D.querySelector(s):s,a={src:u,networkState:k.NETWORK_EMPTY,readyState:k.HAVE_NOTHING,seeking:false,autoplay:u,preload:u,controls:false,loop:false,poster:u,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},M=false,f=false,c=false,J=false,T=false,w,S=true,b=[],Q=true,g=false;k._eventNamespace=n.guid("HTMLJWPlayerVideoElement::");k.parentNode=x;k._util.type="JWPlayer";k.play=function(){k.dispatchEvent("play");a.paused= +false;if(J){if(a.ended){h(0);a.ended=false}w.play(true)}else H(function(){k.play()})};k.pause=function(){a.paused=true;J?w.pause(true):H(function(){k.pause()})};Object.defineProperties(k,{src:{get:function(){return a.src},set:function(r){r&&r!==a.src&&q(r)}},autoplay:{get:function(){return a.autoplay},set:function(r){a.autoplay=k._util.isAttributeSet(r)}},loop:{get:function(){return a.loop},set:function(r){a.loop=k._util.isAttributeSet(r)}},width:{get:function(){return k.parentNode.offsetWidth}}, +height:{get:function(){return k.parentNode.offsetHeight}},currentTime:{get:function(){return a.currentTime},set:function(r){h(r)}},duration:{get:function(){return w.getDuration()}},ended:{get:function(){return a.ended}},paused:{get:function(){return a.paused}},seeking:{get:function(){return a.seeking}},readyState:{get:function(){return a.readyState}},networkState:{get:function(){return a.networkState}},volume:{get:function(){return a.volume},set:function(r){if(r<0||r>1)throw"Volume value must be between 0.0 and 1.0"; +z(r)}},muted:{get:function(){return a.muted},set:function(r){l(k._util.isAttributeSet(r))}},error:{get:function(){return a.error}},buffered:{get:function(){return{start:function(r){if(r===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(r){if(r===0){r=w.getDuration();if(!r)return 0;return r*(w.getBuffer()/100)}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}}}});k._canPlaySrc=n.HTMLJWPlayerVideoElement._canPlaySrc;k.canPlayType=n.HTMLJWPlayerVideoElement.canPlayType;return k}var u= +"",v=false,K=false,I=[];n.HTMLJWPlayerVideoElement=function(s){return new e(s)};n.HTMLJWPlayerVideoElement._canPlaySrc=function(s){if(typeof s=="string"){if(/.+\.+/g.exec(s))return"probably"}else return"probably"};n.HTMLJWPlayerVideoElement.canPlayType=function(){return"probably"}})(Popcorn,window,document);(function(n,A){function D(e){this.startTime=0;this.currentTime=e.currentTime||0;this.duration=e.duration||NaN;this.playInterval=null;this.paused=true;this.playbackRate=this.defaultPlaybackRate=1;this.ended=e.endedCallback||n.nop}function O(e){function u(o){h.push(o)}function v(){if(!P)return 0;return C.currentTime}function K(o){if(o!==v())if(P){q.seeking=true;j.dispatchEvent("seeking");C.seekTo(o);q.ended=false;q.seeking=false;j.dispatchEvent("timeupdate");j.dispatchEvent("seeked");j.dispatchEvent("canplay"); +j.dispatchEvent("canplaythrough")}else u(function(){K(o)})}function I(){j.dispatchEvent("timeupdate")}function s(){q.paused=true;clearInterval(N);j.dispatchEvent("pause")}function H(){if(q.loop){K(0);j.play()}else{q.ended=true;s();j.dispatchEvent("timeupdate");j.dispatchEvent("ended")}}var j=new n._MediaElementProto,y=typeof e==="string"?A.querySelector(e):e,F=A.createElement("div"),P=false,C,q={src:p,networkState:j.NETWORK_EMPTY,readyState:j.HAVE_NOTHING,autoplay:p,preload:p,controls:p,loop:false, +poster:p,volume:1,muted:false,width:y.width|0?y.width:j._util.MIN_WIDTH,height:y.height|0?y.height:j._util.MIN_HEIGHT,seeking:false,ended:false,paused:1,error:null},h=[],N;j._eventNamespace=n.guid("HTMLNullVideoElement::");j.parentNode=y;j._util.type="NullVideo";j.play=function(){if(P){C.play();if(q.paused){if(q.paused===1){q.paused=false;j.dispatchEvent("play");j.dispatchEvent("playing")}else{if(q.ended){K(0);q.ended=false}if(q.paused){q.paused=false;q.loop||j.dispatchEvent("play");j.dispatchEvent("playing")}}N= +setInterval(I,j._util.TIMEUPDATE_MS)}}else u(function(){j.play()})};j.pause=function(){if(P){C.pause();q.paused||s()}else u(function(){j.pause()})};Object.defineProperties(j,{src:{get:function(){return q.src},set:function(o){if(o&&o!==q.src)if(j._canPlaySrc(o)){q.src=o;if(P)if(P&&C){C.pause();C=null;y.removeChild(F);F=A.createElement("div")}F.width=q.width;F.height=q.height;y.appendChild(F);o=E.exec(o);C=new D({currentTime:+o[1],duration:+o[2],endedCallback:H});j.dispatchEvent("loadstart");j.dispatchEvent("progress"); +j.dispatchEvent("durationchange");P=true;q.networkState=j.NETWORK_IDLE;q.readyState=j.HAVE_METADATA;j.dispatchEvent("loadedmetadata");j.dispatchEvent("loadeddata");q.readyState=j.HAVE_FUTURE_DATA;j.dispatchEvent("canplay");q.readyState=j.HAVE_ENOUGH_DATA;for(j.dispatchEvent("canplaythrough");h.length;){o=h.shift();o()}q.autoplay&&j.play()}else{q.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};j.dispatchEvent("error")}}},autoplay:{get:function(){return q.autoplay}, +set:function(o){q.autoplay=j._util.isAttributeSet(o)}},loop:{get:function(){return q.loop},set:function(o){q.loop=j._util.isAttributeSet(o)}},width:{get:function(){return F.width},set:function(o){F.width=o;q.width=F.width}},height:{get:function(){return F.height},set:function(o){F.height=o;q.height=F.height}},currentTime:{get:function(){return v()},set:function(o){K(o)}},duration:{get:function(){return C?C.duration:NaN}},ended:{get:function(){return q.ended}},paused:{get:function(){return q.paused}}, +seeking:{get:function(){return q.seeking}},readyState:{get:function(){return q.readyState}},networkState:{get:function(){return q.networkState}},volume:{get:function(){return q.volume},set:function(o){if(o<0||o>1)throw"Volume value must be between 0.0 and 1.0";q.volume=o;j.dispatchEvent("volumechange")}},muted:{get:function(){return q.muted},set:function(o){o=j._util.isAttributeSet(o);q.muted=o;j.dispatchEvent("volumechange")}},playbackRate:{get:function(){return C.playbackRate},set:function(o){C.playbackRate= +o;j.dispatchEvent("ratechange")}},error:{get:function(){return q.error}}});j._canPlaySrc=n.HTMLNullVideoElement._canPlaySrc;j.canPlayType=n.HTMLNullVideoElement.canPlayType;return j}var p="",E=/#t=(\d+\.?\d*)?,?(\d+\.?\d*)/;D.prototype={play:function(){var e=this;if(this.paused){this.paused=false;this.startTime=Date.now();this.playInterval=setInterval(function(){e.currentTime+=(Date.now()-e.startTime)/(1E3/e.playbackRate);e.startTime=Date.now();if(e.currentTime>=e.duration){e.pause(e.duration);e.ended()}e.currentTime< +0&&e.pause(0)},16)}},pause:function(){if(!this.paused){this.paused=true;clearInterval(this.playInterval)}},seekTo:function(e){e=e<0?0:e;this.currentTime=e=e>this.duration?this.duration:e}};n.HTMLNullVideoElement=function(e){return new O(e)};n.HTMLNullVideoElement._canPlaySrc=function(e){return E.test(e)?"probably":p};n.HTMLNullVideoElement.canPlayType=function(e){return e==="video/x-nullvideo"?"probably":p}})(Popcorn,document);(function(n,A,D){function O(){if(!K){n.getScript("https://w.soundcloud.com/player/api.js",function(){n.getScript("https://connect.soundcloud.com/sdk.js",function(){v=true;SC.initialize({client_id:"PRaNFlda6Bhf5utPjUsptg"});for(var s=I.length;s--;){I[s]();delete I[s]}})});K=true}return v}function p(s){I.unshift(s)}function E(s){function H(b){J.unshift(b)}function j(){c.bind(SC.Widget.Events.LOAD_PROGRESS,function(b){N({type:"loadProgress",data:b.currentPosition/1E3})});c.bind(SC.Widget.Events.PLAY_PROGRESS, +function(b){N({type:"playProgress",data:b.currentPosition/1E3})});c.bind(SC.Widget.Events.PLAY,function(){N({type:"play"})});c.bind(SC.Widget.Events.PAUSE,function(){N({type:"pause"})});c.bind(SC.Widget.Events.SEEK,function(){c.getPosition(function(b){b=b/1E3;if(a.seeking)if(Math.floor(b)!==Math.floor(a.currentTime))c.seekTo(a.currentTime*1E3);else{a.ended=false;a.seeking=false;l.dispatchEvent("timeupdate");l.dispatchEvent("seeked");l.dispatchEvent("canplay");l.dispatchEvent("canplaythrough")}else N({type:"seek", +data:b})})});c.bind(SC.Widget.Events.FINISH,function(){N({type:"finish"})});M=true;c.getDuration(F)}function y(){c.bind(SC.Widget.Events.PLAY_PROGRESS,function(b){c.setVolume(0);if(b.currentPosition>0){c.unbind(SC.Widget.Events.PLAY_PROGRESS);c.bind(SC.Widget.Events.PAUSE,function(){c.unbind(SC.Widget.Events.PAUSE);c.setVolume(1);c.bind(SC.Widget.Events.SEEK,function(){c.unbind(SC.Widget.Events.SEEK);j()});c.seekTo(0)});c.pause()}});c.play()}function F(b){b/=1E3;var Q=a.duration;if(Q!==b){a.duration= +b;l.dispatchEvent("durationchange");if(isNaN(Q)){a.networkState=l.NETWORK_IDLE;a.readyState=l.HAVE_METADATA;l.dispatchEvent("loadedmetadata");l.dispatchEvent("loadeddata");a.readyState=l.HAVE_FUTURE_DATA;l.dispatchEvent("canplay");a.readyState=l.HAVE_ENOUGH_DATA;l.dispatchEvent("canplaythrough");for(b=J.length;b--;){J[b]();delete J[b]}a.paused&&a.autoplay&&l.play()}}}function P(b){function Q(){a.seeking=true;l.dispatchEvent("seeking");c.seekTo(b)}a.currentTime=b;b*=1E3;M?Q():addMediaReadyCallback(Q)} +function C(){a.paused=true;if(!f){f=true;clearInterval(T);l.dispatchEvent("pause")}}function q(){l.dispatchEvent("timeupdate")}function h(b){a.currentTime=b;b!==S&&l.dispatchEvent("timeupdate");S=b}function N(b){switch(b.type){case "loadProgress":l.dispatchEvent("progress");break;case "playProgress":h(b.data);break;case "play":if(!w){w=setInterval(o,e);a.loop&&l.dispatchEvent("play")}T=setInterval(q,l._util.TIMEUPDATE_MS);a.paused=false;if(f){f=false;a.loop||l.dispatchEvent("play");l.dispatchEvent("playing")}break; +case "pause":C();break;case "finish":if(a.loop){P(0);l.play()}else{a.ended=true;l.pause();C();l.dispatchEvent("timeupdate");l.dispatchEvent("ended")}break;case "seek":h(b.data)}}function o(){a.ended||c.getPosition(function(b){h(b/1E3)})}function m(b){if(l._canPlaySrc(b)){a.src=b;if(M)if(M&&c){clearInterval(w);c.pause();c.unbind(SC.Widget.Events.READY);c.unbind(SC.Widget.Events.LOAD_PROGRESS);c.unbind(SC.Widget.Events.PLAY_PROGRESS);c.unbind(SC.Widget.Events.PLAY);c.unbind(SC.Widget.Events.PAUSE); +c.unbind(SC.Widget.Events.SEEK);c.unbind(SC.Widget.Events.FINISH);k.removeChild(x);x=D.createElement("iframe")}if(O()){M=false;SC.get("/resolve",{url:b},function(Q){var g;if(Q.errors){g={name:"MediaError"};if(Q.errors[0])if(Q.errors[0].error_message==="404 - Not Found"){g.message="Video not found.";g.code=MediaError.MEDIA_ERR_NETWORK}a.error=g;l.dispatchEvent("error")}x.id=n.guid("soundcloud-");x.width=a.width;x.height=a.height;x.frameBorder=0;x.webkitAllowFullScreen=true;x.mozAllowFullScreen=true; +x.allowFullScreen=true;z(a.controls);k.appendChild(x);x.onload=function(){x.onload=null;c=SC.Widget(x);c.bind(SC.Widget.Events.READY,y);a.networkState=l.NETWORK_LOADING;l.dispatchEvent("loadstart");l.dispatchEvent("progress")};x.src="https://w.soundcloud.com/player/?url="+Q.uri+"&show_artwork=false&buying=false&liking=false&sharing=false&download=false&show_comments=false&show_user=false&single_active=false"})}else p(function(){m(b)})}else{a.error={name:"MediaError",message:"Media Source Not Supported", +code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};l.dispatchEvent("error")}}function B(b){a.volume=b;if(M){c.setVolume(b);l.dispatchEvent("volumechange")}else H(function(){B(b)})}function L(b){if(M)if(b){a.muted=a.volume;B(0)}else{a.muted=0;B(a.muted)}else{a.muted=b?1:0;H(function(){L(b)})}}function z(b){if(M){x.style.position="absolute";x.style.visibility=b?"visible":"hidden"}else{x.style.opacity=b?"1":"0";x.style.pointerEvents=b?"auto":"none"}a.controls=b}if(!A.postMessage)throw"ERROR: HTMLSoundCloudAudioElement requires window.postMessage"; +var l=new n._MediaElementProto,k=typeof s==="string"?n.dom.find(s):s,x=D.createElement("iframe"),a={src:u,networkState:l.NETWORK_EMPTY,readyState:l.HAVE_NOTHING,seeking:false,autoplay:u,preload:u,controls:false,loop:false,poster:u,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,width:k.width|0?k.width:l._util.MIN_WIDTH,height:k.height|0?k.height:l._util.MIN_HEIGHT,error:null},M=false,f=true,c,J=[],T,w,S=0;l._eventNamespace=n.guid("HTMLSoundCloudAudioElement::");l.parentNode=k; +l._util.type="SoundCloud";l.play=function(){a.paused=false;if(M){a.ended&&P(0);c.play()}else H(function(){l.play()})};l.pause=function(){a.paused=true;M?c.pause():H(function(){l.pause()})};Object.defineProperties(l,{src:{get:function(){return a.src},set:function(b){b&&b!==a.src&&m(b)}},autoplay:{get:function(){return a.autoplay},set:function(b){a.autoplay=l._util.isAttributeSet(b)}},loop:{get:function(){return a.loop},set:function(b){a.loop=l._util.isAttributeSet(b)}},width:{get:function(){return x.width}, +set:function(b){x.width=b;a.width=x.width}},height:{get:function(){return x.height},set:function(b){x.height=b;a.height=x.height}},currentTime:{get:function(){return a.currentTime},set:function(b){P(b)}},duration:{get:function(){return a.duration}},ended:{get:function(){return a.ended}},paused:{get:function(){return a.paused}},seeking:{get:function(){return a.seeking}},readyState:{get:function(){return a.readyState}},networkState:{get:function(){return a.networkState}},volume:{get:function(){return a.muted> +0?a.muted:a.volume},set:function(b){if(b<0||b>1)throw"Volume value must be between 0.0 and 1.0";B(b)}},muted:{get:function(){return a.muted>0},set:function(b){L(l._util.isAttributeSet(b))}},error:{get:function(){return a.error}},controls:{get:function(){return a.controls},set:function(b){z(!!b)}}});l._canPlaySrc=n.HTMLSoundCloudAudioElement._canPlaySrc;l.canPlayType=n.HTMLSoundCloudAudioElement.canPlayType;return l}var e=16,u="",v=false,K=false,I=[];n.HTMLSoundCloudAudioElement=function(s){return new E(s)}; +n.HTMLSoundCloudAudioElement._canPlaySrc=function(s){return/(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/.test(s)?"probably":u};n.HTMLSoundCloudAudioElement.canPlayType=function(s){return s==="audio/x-soundcloud"?"probably":u}})(Popcorn,window,document);(function(n,A,D){function O(v){var K=this,I=v.src.split("?")[0];if(I.substr(0,2)==="//")I=A.location.protocol+I;"play pause paused seekTo unload getCurrentTime getDuration getVideoEmbedCode getVideoHeight getVideoWidth getVideoUrl getColor setColor setLoop getVolume setVolume addEventListener".split(" ").forEach(function(s){K[s]=function(H){H=JSON.stringify({method:s,value:H});v.contentWindow&&v.contentWindow.postMessage(H,I)}})}function p(v){function K(f){k.unshift(f)}function I(f){var c=m.duration; +if(c!==f){m.duration=f;h.dispatchEvent("durationchange");if(isNaN(c)){m.networkState=h.NETWORK_IDLE;m.readyState=h.HAVE_METADATA;h.dispatchEvent("loadedmetadata");h.dispatchEvent("loadeddata");m.readyState=h.HAVE_FUTURE_DATA;h.dispatchEvent("canplay");m.readyState=h.HAVE_ENOUGH_DATA;h.dispatchEvent("canplaythrough");m.autoplay&&h.play();for(f=k.length;f--;){k[f]();delete k[f]}}}}function s(f){if(B){m.seeking=true;h.dispatchEvent("seeking");z.seekTo(f)}else K(function(){s(f)})}function H(){h.dispatchEvent("timeupdate")} +function j(f){(m.currentTime=f)!==M&&h.dispatchEvent("timeupdate");M=m.currentTime}function y(f){if(f.origin===u){var c;try{c=JSON.parse(f.data)}catch(J){console.warn(J)}if(c.player_id==L)switch(c.event){case "ready":z=new O(o);z.addEventListener("loadProgress");z.addEventListener("pause");z.setVolume(0);z.play();break;case "loadProgress":if(parseFloat(c.data.duration)>0&&!B){B=true;z.pause()}break;case "pause":z.setVolume(1);A.removeEventListener("message",y,false);A.addEventListener("message",F, +false);z.addEventListener("loadProgress");z.addEventListener("playProgress");z.addEventListener("play");z.addEventListener("pause");z.addEventListener("finish");z.addEventListener("seek");z.getDuration();m.networkState=h.NETWORK_LOADING;h.dispatchEvent("loadstart");h.dispatchEvent("progress")}}}function F(f){if(f.origin===u){var c;try{c=JSON.parse(f.data)}catch(J){console.warn(J)}if(c.player_id==L){switch(c.method){case "getCurrentTime":j(parseFloat(c.value));break;case "getDuration":I(parseFloat(c.value)); +break;case "getVolume":f=parseFloat(c.value);if(m.volume!==f){m.volume=f;h.dispatchEvent("volumechange")}}switch(c.event){case "loadProgress":h.dispatchEvent("progress");I(parseFloat(c.data.duration));break;case "playProgress":j(parseFloat(c.data.seconds));break;case "play":m.ended&&s(0);if(!a){a=setInterval(P,E);m.loop&&h.dispatchEvent("play")}x=setInterval(H,h._util.TIMEUPDATE_MS);m.paused=false;if(l){l=false;m.loop||h.dispatchEvent("play");h.dispatchEvent("playing")}break;case "pause":m.paused= +true;if(!l){l=true;clearInterval(x);h.dispatchEvent("pause")}break;case "finish":if(m.loop){s(0);h.play()}else{m.ended=true;h.dispatchEvent("ended")}break;case "seek":j(parseFloat(c.data.seconds));m.seeking=false;h.dispatchEvent("timeupdate");h.dispatchEvent("seeked");h.dispatchEvent("canplay");h.dispatchEvent("canplaythrough")}}}}function P(){z.getCurrentTime()}function C(f){m.volume=f;if(B){z.setVolume(f);h.dispatchEvent("volumechange")}else K(function(){C(f)})}function q(f){if(B)if(f){m.muted= +m.volume;C(0)}else{m.muted=0;C(m.muted)}else{m.muted=f?1:0;K(function(){q(f)})}}if(!A.postMessage)throw"ERROR: HTMLVimeoVideoElement requires window.postMessage";var h=new n._MediaElementProto,N=typeof v==="string"?n.dom.find(v):v,o=D.createElement("iframe"),m={src:e,networkState:h.NETWORK_EMPTY,readyState:h.HAVE_NOTHING,seeking:false,autoplay:e,preload:e,controls:false,loop:false,poster:e,volume:1,muted:0,currentTime:0,duration:NaN,ended:false,paused:true,error:null},B=false,L=n.guid(),z,l=true, +k=[],x,a,M=0;h._eventNamespace=n.guid("HTMLVimeoVideoElement::");h.parentNode=N;h._util.type="Vimeo";h.play=function(){m.paused=false;B?z.play():K(function(){h.play()})};h.pause=function(){m.paused=true;B?z.pause():K(function(){h.pause()})};Object.defineProperties(h,{src:{get:function(){return m.src},set:function(f){if(f&&f!==m.src)if(h._canPlaySrc(f)){m.src=f;if(B)if(B&&z){clearInterval(a);z.pause();A.removeEventListener("message",F,false);N.removeChild(o);o=D.createElement("iframe")}B=false;f=h._util.parseUri(f); +var c=f.queryKey,J,T=["api=1","player_id="+L,"title=0","byline=0","portrait=0"];m.loop=c.loop==="1"||m.loop;delete c.loop;m.autoplay=c.autoplay==="1"||m.autoplay;delete c.autoplay;f=u+"/video/"+/\d+$/.exec(f.path)+"?";for(J in c)c.hasOwnProperty(J)&&T.push(encodeURIComponent(J)+"="+encodeURIComponent(c[J]));f+=T.join("&");o.id=L;o.style.width="100%";o.style.height="100%";o.frameBorder=0;o.webkitAllowFullScreen=true;o.mozAllowFullScreen=true;o.allowFullScreen=true;N.appendChild(o);o.src=f;A.addEventListener("message", +y,false)}else{m.error={name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};h.dispatchEvent("error")}}},autoplay:{get:function(){return m.autoplay},set:function(f){m.autoplay=h._util.isAttributeSet(f)}},loop:{get:function(){return m.loop},set:function(f){m.loop=h._util.isAttributeSet(f)}},width:{get:function(){return h.parentNode.offsetWidth}},height:{get:function(){return h.parentNode.offsetHeight}},currentTime:{get:function(){return m.currentTime}, +set:function(f){s(f)}},duration:{get:function(){return m.duration}},ended:{get:function(){return m.ended}},paused:{get:function(){return m.paused}},seeking:{get:function(){return m.seeking}},readyState:{get:function(){return m.readyState}},networkState:{get:function(){return m.networkState}},volume:{get:function(){return m.muted>0?m.muted:m.volume},set:function(f){if(f<0||f>1)throw"Volume value must be between 0.0 and 1.0";C(f)}},muted:{get:function(){return m.muted>0},set:function(f){q(h._util.isAttributeSet(f))}}, +error:{get:function(){return m.error}}});h._canPlaySrc=n.HTMLVimeoVideoElement._canPlaySrc;h.canPlayType=n.HTMLVimeoVideoElement.canPlayType;return h}var E=16,e="",u="https://player.vimeo.com";n.HTMLVimeoVideoElement=function(v){return new p(v)};n.HTMLVimeoVideoElement._canPlaySrc=function(v){return/player.vimeo.com\/video\/\d+/.test(v)||/vimeo.com\/\d+/.test(v)?"probably":e};n.HTMLVimeoVideoElement.canPlayType=function(v){return v==="video/x-vimeo"?"probably":e}})(Popcorn,window,document);(function(n,A,D){function O(){var y;if(YT.loaded)for(s=true;j.length;){y=j.shift();y()}else setTimeout(O,250)}function p(){var y;if(!H){if(A.YT)O();else{y=D.createElement("script");y.addEventListener("load",O,false);y.src="https://www.youtube.com/iframe_api";D.head.appendChild(y)}H=true}return s}function E(y){j.push(y)}function e(y){function F(i){W.push(i)}function P(){G.pauseVideo();L("play",P);B("play",w)}function C(){B("pause",S);L("pause",C)}function q(){var i=function(){if(G.isMuted()){B("play", +m);G.playVideo()}else setTimeout(i,0)};V=true;G.mute();i()}function h(i){var t={name:"MediaError"};switch(i.data){case 2:t.message="Invalid video parameter.";t.code=MediaError.MEDIA_ERR_ABORTED;break;case 5:t.message="The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.";t.code=MediaError.MEDIA_ERR_DECODE;case 100:t.message="Video not found.";t.code=MediaError.MEDIA_ERR_NETWORK;break;case 101:case 150:t.message="Video not usable.";t.code= +MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;break;default:t.message="Unknown error.";t.code=5}d.error=t;g.dispatchEvent("error")}function N(){B("play",w);B("pause",S);if(d.autoplay||!d.paused){L("play",N);d.paused=false;F(function(){d.paused||w()})}d.muted||G.unMute();d.readyState=g.HAVE_METADATA;g.dispatchEvent("loadedmetadata");$=setInterval(a,u);g.dispatchEvent("loadeddata");d.readyState=g.HAVE_FUTURE_DATA;g.dispatchEvent("canplay");U=true;for(aa=setInterval(M,50);W.length;){W[0]();W.shift()}d.readyState= +g.HAVE_ENOUGH_DATA;g.dispatchEvent("canplaythrough")}function o(){L("pause",o);if(G.getCurrentTime()>0)setTimeout(o,0);else if(d.autoplay||!d.paused){B("play",N);G.playVideo()}else N()}function m(){L("play",m);if(G.getCurrentTime()===0)setTimeout(m,0);else{B("pause",o);G.seekTo(0);G.pauseVideo()}}function B(i,t){g.addEventListener("youtube-"+i,t,false)}function L(i,t){g.removeEventListener("youtube-"+i,t,false)}function z(i){g.dispatchEvent("youtube-"+i)}function l(){d.networkState=g.NETWORK_LOADING; +var i=G.getDuration();if(d.duration!==i){d.duration=i;g.dispatchEvent("durationchange")}g.dispatchEvent("waiting")}function k(i){switch(i.data){case YT.PlayerState.ENDED:z("ended");break;case YT.PlayerState.PLAYING:z("play");break;case YT.PlayerState.PAUSED:G.getDuration()!==G.getCurrentTime()&&z("pause");break;case YT.PlayerState.BUFFERING:z("buffering")}i.data!==YT.PlayerState.BUFFERING&&ba===YT.PlayerState.BUFFERING&&g.dispatchEvent("progress");ba=i.data}function x(i){if(g._canPlaySrc(i)){d.src= +i;if(p()){if(V)if(U){if(V&&G){L("buffering",l);L("ended",b);L("play",w);L("pause",S);S();Y=U=false;d.currentTime=0;W=[];clearInterval($);clearInterval(aa);G.stopVideo();G.clearVideo();G.destroy();R=D.createElement("div")}}else{F(function(){x(i)});return}r.appendChild(R);var t=g._util.parseUri(i).queryKey;delete t.v;d.autoplay=t.autoplay==="1"||d.autoplay;delete t.autoplay;d.loop=t.loop==="1"||d.loop;delete t.loop;t.rel=t.rel||0;t.modestbranding=t.modestbranding||1;t.iv_load_policy=t.iv_load_policy|| +3;t.disablekb=t.disablekb||1;t.showinfo=t.showinfo||0;var da=A.location.protocol==="file:"?"*":A.location.protocol+"//"+A.location.host;t.origin=t.origin||da;t.controls=t.controls||d.controls?2:0;d.controls=t.controls;t.wmode=t.wmode||"opaque";if(t.html5!==0)t.html5=1;i=K.exec(i)[1];G=new YT.Player(R,{width:"100%",height:"100%",wmode:t.wmode,videoId:i,playerVars:t,events:{onReady:q,onError:h,onStateChange:k}});d.networkState=g.NETWORK_LOADING;g.dispatchEvent("loadstart");g.dispatchEvent("progress")}else E(function(){x(i)})}else{d.error= +{name:"MediaError",message:"Media Source Not Supported",code:MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED};g.dispatchEvent("error")}}function a(){var i=G.getCurrentTime();if(d.seeking)I(i-d.currentTime)<1&&T();else{if(I(d.currentTime-i)>u){J();T()}d.currentTime=i}}function M(){var i=G.getVideoLoadedFraction();if(i&&Z!==i){Z=i;g.dispatchEvent("progress")}}function f(i){if(i!==d.currentTime){d.currentTime=i;if(U){J();G.seekTo(i)}else F(function(){J();G.seekTo(i)})}}function c(){g.dispatchEvent("timeupdate")} +function J(){B("pause",C);L("pause",S);d.seeking=true;g.dispatchEvent("seeking")}function T(){d.ended=false;d.seeking=false;g.dispatchEvent("timeupdate");g.dispatchEvent("seeked");g.dispatchEvent("canplay");g.dispatchEvent("canplaythrough")}function w(){if(d.ended){f(0);d.ended=false}ca=setInterval(c,g._util.TIMEUPDATE_MS);d.paused=false;if(X){X=false;if(d.loop&&!Y||!d.loop){Y=true;g.dispatchEvent("play")}g.dispatchEvent("playing")}}function S(){d.paused=true;if(!X){X=true;clearInterval(ca);g.dispatchEvent("pause")}} +function b(){if(d.loop){f(0);g.play()}else{d.ended=true;S();B("play",P);L("play",w);g.dispatchEvent("timeupdate");g.dispatchEvent("ended")}}function Q(i){d.muted=i;if(U){G[i?"mute":"unMute"]();g.dispatchEvent("volumechange")}else F(function(){Q(d.muted)})}if(!A.postMessage)throw"ERROR: HTMLYouTubeVideoElement requires window.postMessage";var g=new n._MediaElementProto,r=typeof y==="string"?D.querySelector(y):y,R=D.createElement("div"),d={src:v,networkState:g.NETWORK_EMPTY,readyState:g.HAVE_NOTHING, +seeking:false,autoplay:v,preload:v,controls:false,loop:false,poster:v,volume:1,muted:false,currentTime:0,duration:NaN,ended:false,paused:true,error:null},V=false,U=false,Y=false,G,X=true,W=[],ba=-1,aa,Z=0,$,ca;g._eventNamespace=n.guid("HTMLYouTubeVideoElement::");g.parentNode=r;g._util.type="YouTube";B("buffering",l);B("ended",b);g.play=function(){d.paused=false;U?G.playVideo():F(function(){g.play()})};g.pause=function(){d.paused=true;if(U){C();G.pauseVideo()}else F(function(){g.pause()})};Object.defineProperties(g, +{src:{get:function(){return d.src},set:function(i){i&&i!==d.src&&x(i)}},autoplay:{get:function(){return d.autoplay},set:function(i){d.autoplay=g._util.isAttributeSet(i)}},loop:{get:function(){return d.loop},set:function(i){d.loop=g._util.isAttributeSet(i)}},width:{get:function(){return g.parentNode.offsetWidth}},height:{get:function(){return g.parentNode.offsetHeight}},currentTime:{get:function(){return d.currentTime},set:function(i){f(i)}},duration:{get:function(){return d.duration}},ended:{get:function(){return d.ended}}, +paused:{get:function(){return d.paused}},seeking:{get:function(){return d.seeking}},readyState:{get:function(){return d.readyState}},networkState:{get:function(){return d.networkState}},volume:{get:function(){return d.volume},set:function(i){if(i<0||i>1)throw"Volume value must be between 0.0 and 1.0";d.volume=i;if(U){G.setVolume(d.volume*100);g.dispatchEvent("volumechange")}else F(function(){g.volume=i})}},muted:{get:function(){return d.muted},set:function(i){Q(g._util.isAttributeSet(i))}},error:{get:function(){return d.error}}, +buffered:{get:function(){return{start:function(i){if(i===0)return 0;throw"INDEX_SIZE_ERR: DOM Exception 1";},end:function(i){if(i===0){if(!d.duration)return 0;return d.duration*Z}throw"INDEX_SIZE_ERR: DOM Exception 1";},length:1}},configurable:true}});g._canPlaySrc=n.HTMLYouTubeVideoElement._canPlaySrc;g.canPlayType=n.HTMLYouTubeVideoElement.canPlayType;return g}var u=10,v="",K=/^.*(?:\/|v=)(.{11})/,I=Math.abs,s=false,H=false,j=[];n.HTMLYouTubeVideoElement=function(y){return new e(y)};n.HTMLYouTubeVideoElement._canPlaySrc= +function(y){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu).*(?:\/|v=)(.{11})/.test(y)?"probably":v};n.HTMLYouTubeVideoElement.canPlayType=function(y){return y==="video/x-youtube"?"probably":v}})(Popcorn,window,document);