diff -r 9246dce433f8 -r e84a7100cc3f src/js/libs/popcorn.js --- a/src/js/libs/popcorn.js Thu Dec 08 11:23:08 2011 +0100 +++ b/src/js/libs/popcorn.js Thu Dec 08 12:29:04 2011 +0100 @@ -10,7 +10,7 @@ "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + "timeUpdate plugin removePlugin compose effect parser xhr getJSONP getScript" ).split(/\s+/); - while( methods.length ) { + while ( methods.length ) { global.Popcorn[ methods.shift() ] = function() {}; } return; @@ -58,6 +58,61 @@ }; }()), + refresh = function( obj ) { + var currentTime = obj.media.currentTime, + animation = obj.options.frameAnimation, + disabled = obj.data.disabled, + tracks = obj.data.trackEvents, + animating = tracks.animating, + start = tracks.startIndex, + registryByName = Popcorn.registryByName, + animIndex = 0, + byStart, natives, type; + + start = Math.min( start + 1, tracks.byStart.length - 2 ); + + while ( start > 0 && tracks.byStart[ start ] ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + + if ( !natives || + ( !!registryByName[ type ] || !!obj[ type ] ) ) { + + if ( ( byStart.start <= currentTime && byStart.end > currentTime ) && + disabled.indexOf( type ) === -1 ) { + + if ( !byStart._running ) { + byStart._running = true; + natives.start.call( obj, null, byStart ); + + // if the 'frameAnimation' option is used, + // push the current byStart object into the `animating` cue + if ( animation && + ( byStart && byStart._running && byStart.natives.frame ) ) { + + natives.frame.call( obj, null, byStart, currentTime ); + } + } + } else if ( byStart._running === true ) { + + byStart._running = false; + natives.end.call( obj, null, byStart ); + + if ( animation && byStart._natives.frame ) { + animIndex = animating.indexOf( byStart ); + if ( animIndex >= 0 ) { + animating.splice( animIndex, 1 ); + } + } + } + } + + start--; + } + }, + // Declare constructor // Returns an instance object. Popcorn = function( entity, options ) { @@ -147,6 +202,9 @@ this.data = { + // Executed by either timeupdate event or in rAF loop + timeUpdate: Popcorn.nop, + // Allows disabling a plugin per instance disabled: [], @@ -188,7 +246,7 @@ // Wrap true ready check var isReady = function( that ) { - var duration, videoDurationPlus, animate; + var duration, videoDurationPlus; if ( that.media.readyState >= 2 ) { // Adding padding to the front and end of the arrays @@ -208,25 +266,25 @@ // 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) - animate = function () { + that.data.timeUpdate = function () { Popcorn.timeUpdate( that, {} ); that.trigger( "timeupdate" ); - requestAnimFrame( animate ); + !that.isDestroyed && requestAnimFrame( that.data.timeUpdate ); }; - requestAnimFrame( animate ); + !that.isDestroyed && requestAnimFrame( that.data.timeUpdate ); } else { - that.data.timeUpdateFunction = function( event ) { + that.data.timeUpdate = function( event ) { Popcorn.timeUpdate( that, event ); }; if ( !that.isDestroyed ) { - that.media.addEventListener( "timeupdate", that.data.timeUpdateFunction, false ); + that.media.addEventListener( "timeupdate", that.data.timeUpdate, false ); } } } else { @@ -357,6 +415,8 @@ disabled.push( plugin ); } + refresh( instance ); + return instance; }, enable: function( instance, plugin ) { @@ -368,6 +428,8 @@ disabled.splice( index, 1 ); } + refresh( instance ); + return instance; }, destroy: function( instance ) { @@ -384,7 +446,7 @@ } if ( !instance.isDestroyed ) { - instance.media.removeEventListener( "timeupdate", instance.data.timeUpdateFunction, false ); + instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); instance.isDestroyed = true; } } @@ -780,25 +842,60 @@ // Store this definition in an array sorted by times var byStart = obj.data.trackEvents.byStart, byEnd = obj.data.trackEvents.byEnd, - idx; - - for ( idx = byStart.length - 1; idx >= 0; idx-- ) { - - if ( track.start >= byStart[ idx ].start ) { - byStart.splice( idx + 1, 0, track ); + startIndex, endIndex, + currentTime; + + 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; } } - for ( idx = byEnd.length - 1; idx >= 0; idx-- ) { - - if ( track.end > byEnd[ idx ].end ) { - byEnd.splice( idx + 1, 0, track ); - break; + // Display track event immediately if it's enabled and current + if ( track._natives && + ( !!Popcorn.registryByName[ track._natives.type ] || !!obj[ track._natives.type ] ) ) { + + currentTime = obj.media.currentTime; + if ( track.end > currentTime && + track.start <= currentTime && + obj.data.disabled.indexOf( track._natives.type ) === -1 ) { + + track._running = true; + track._natives.start.call( obj, null, track ); + + if ( obj.options.frameAnimation && + track._natives.frame ) { + + obj.data.trackEvents.animating.push( track ); + track._natives.frame.call( obj, null, track, currentTime ); + } } } - this.timeUpdate( obj, null ); + // update startIndex and endIndex + if ( startIndex <= obj.data.trackEvents.startIndex && + track.start <= obj.data.trackEvents.previousUpdateTime ) { + + obj.data.trackEvents.startIndex++; + } + + if ( endIndex <= obj.data.trackEvents.endIndex && + track.end < obj.data.trackEvents.previousUpdateTime ) { + + obj.data.trackEvents.endIndex++; + } + + this.timeUpdate( obj, null, true ); // Store references to user added trackevents in ref table if ( track._id ) { @@ -1209,6 +1306,19 @@ natives.start = natives.start || natives[ "in" ]; natives.end = natives.end || natives[ "out" ]; + // extend teardown to always call end if running + natives._teardown = combineFn(function() { + + var args = slice.call( arguments ); + + // 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 && natives.end.apply( this, args ); + }, natives._teardown ); + // Check for previously set default options defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ]; @@ -1364,6 +1474,7 @@ 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 ]; @@ -1554,10 +1665,18 @@ basePlayer[ val ] = (function( value ) { - return function() { - - return container[ value ].apply( container, arguments ); - }; + // 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 { @@ -1797,7 +1916,8 @@ Popcorn.xhr.httpData = function( settings ) { - var data, json = null; + var data, json = null, + parser, xml = null; settings.ajax.onreadystatechange = function() { @@ -1815,6 +1935,22 @@ 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 ]; @@ -2046,7 +2182,7 @@ // Protected API methods Popcorn.protect = { - natives: getItems() + natives: getItems() }; // Exposes Popcorn to global context