# HG changeset patch # User hamidouk # Date 1323256032 -3600 # Node ID 06d2aa32c5f4b174efbcdf6431b212b5b30a2de6 # Parent f3237cce149ed69424775eeb1990cb6459683ec1# Parent bae7c50704d7710b512bad3ae1efd7200ab2e219 Merge with no-popcorn diff -r bae7c50704d7 -r 06d2aa32c5f4 .hgignore --- a/.hgignore Wed Dec 07 11:51:04 2011 +0100 +++ b/.hgignore Wed Dec 07 12:07:12 2011 +0100 @@ -5,3 +5,4 @@ build/LdtPlayer-release.js build/LdtPlayer.min.js *.swp +*.orig diff -r bae7c50704d7 -r 06d2aa32c5f4 assets/pause_sprite.psd Binary file assets/pause_sprite.psd has changed diff -r bae7c50704d7 -r 06d2aa32c5f4 src/css/LdtPlayer.css --- a/src/css/LdtPlayer.css Wed Dec 07 11:51:04 2011 +0100 +++ b/src/css/LdtPlayer.css Wed Dec 07 12:07:12 2011 +0100 @@ -277,7 +277,7 @@ padding : 3px; z-index: 10000000000; max-width: 200px; - background: transparent url("http://www.polemictweet.com/images/white_arrow_long.png"); + background: transparent url("imgs/white_arrow_long.png"); font-size: 12px; height: 125px; width: 180px; diff -r bae7c50704d7 -r 06d2aa32c5f4 src/css/imgs/pause_sprite.png Binary file src/css/imgs/pause_sprite.png has changed diff -r bae7c50704d7 -r 06d2aa32c5f4 src/css/imgs/white_arrow_long.png Binary file src/css/imgs/white_arrow_long.png has changed diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/init.js --- a/src/js/init.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/init.js Wed Dec 07 12:07:12 2011 +0100 @@ -16,6 +16,13 @@ case "html5": var tmpId = Popcorn.guid("video"); IriSP.jQuery("#" + containerDiv).append(""); + + if (options.hasOwnProperty("width")) + IriSP.jQuery("#" + containerDiv).css("width", options.width); + + if (options.hasOwnProperty("height")) + IriSP.jQuery("#" + containerDiv).css("height", options.height); + pop = Popcorn("#" + tmpId).mediafragment({start : 0}); break; diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/libs/popcorn.code.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/popcorn.code.js Wed Dec 07 12:07:12 2011 +0100 @@ -0,0 +1,182 @@ +// PLUGIN: Code + +(function ( Popcorn ) { + + /** + * Code Popcorn Plug-in + * + * Adds the ability to run arbitrary code (JavaScript functions) according to video timing. + * + * @param {Object} options + * + * Required parameters: start, end, template, data, and target. + * Optional parameter: static. + * + * start: the time in seconds when the mustache template should be rendered + * in the target div. + * + * end: the time in seconds when the rendered mustache template should be + * removed from the target div. + * + * onStart: the function to be run when the start time is reached. + * + * onFrame: [optional] a function to be run on each paint call + * (e.g., called ~60 times per second) between the start and end times. + * + * onEnd: [optional] a function to be run when the end time is reached. + * + * Example: + var p = Popcorn('#video') + + // onStart function only + .code({ + start: 1, + end: 4, + onStart: function( options ) { + // called on start + } + }) + + // onStart + onEnd only + .code({ + start: 6, + end: 8, + onStart: function( options ) { + // called on start + }, + onEnd: function ( options ) { + // called on end + } + }) + + // onStart, onEnd, onFrame + .code({ + start: 10, + end: 14, + onStart: function( options ) { + // called on start + }, + onFrame: function ( options ) { + // called on every paint frame between start and end. + // uses mozRequestAnimationFrame, webkitRequestAnimationFrame, + // or setTimeout with 16ms window. + }, + onEnd: function ( options ) { + // called on end + } + }); + * + */ + + Popcorn.plugin( "code" , function( options ) { + var running = false; + + // Setup a proper frame interval function (60fps), favouring paint events. + var step = (function() { + + var buildFrameRunner = function( runner ) { + return function( f, options ) { + + var _f = function() { + running && f(); + running && runner( _f ); + }; + + _f(); + }; + }; + + // Figure out which level of browser support we have for this + if ( window.webkitRequestAnimationFrame ) { + return buildFrameRunner( window.webkitRequestAnimationFrame ); + } else if ( window.mozRequestAnimationFrame ) { + return buildFrameRunner( window.mozRequestAnimationFrame ); + } else { + return buildFrameRunner( function( f ) { + window.setTimeout( f, 16 ); + }); + } + + })(); + + if ( !options.onStart || typeof options.onStart !== "function" ) { + + if ( Popcorn.plugin.debug ) { + throw new Error( "Popcorn Code Plugin Error: onStart must be a function." ); + } + options.onStart = Popcorn.nop; + } + + if ( options.onEnd && typeof options.onEnd !== "function" ) { + + if ( Popcorn.plugin.debug ) { + throw new Error( "Popcorn Code Plugin Error: onEnd must be a function." ); + } + options.onEnd = undefined; + } + + if ( options.onFrame && typeof options.onFrame !== "function" ) { + + if ( Popcorn.plugin.debug ) { + throw new Error( "Popcorn Code Plugin Error: onFrame must be a function." ); + } + options.onFrame = undefined; + } + + return { + start: function( event, options ) { + options.onStart( options ); + + if ( options.onFrame ) { + running = true; + step( options.onFrame, options ); + } + }, + + end: function( event, options ) { + if ( options.onFrame ) { + running = false; + } + + if ( options.onEnd ) { + options.onEnd( options ); + } + } + }; + }, + { + about: { + name: "Popcorn Code Plugin", + version: "0.1", + author: "David Humphrey (@humphd)", + website: "http://vocamus.net/dave" + }, + options: { + start: { + elem: "input", + type: "text", + label: "In" + }, + end: { + elem: "input", + type: "text", + label: "Out" + }, + onStart: { + elem: "input", + type: "function", + label: "onStart" + }, + onFrame: { + elem: "input", + type: "function", + label: "onFrame" + }, + onEnd: { + elem: "input", + type: "function", + label: "onEnd" + } + } + }); +})( Popcorn ); diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/libs/popcorn.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/popcorn.js Wed Dec 07 12:07:12 2011 +0100 @@ -0,0 +1,2055 @@ +(function(global, document) { + + // Popcorn.js does not support archaic browsers + if ( !document.addEventListener ) { + global.Popcorn = { + isSupported: false + }; + + var methods = ( "forEach extend effects error guid sizeOf isArray nop position disable enable destroy " + + "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + + "timeUpdate plugin removePlugin compose effect parser 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, + + // ID string matching + rIdExp = /^(#([\w\-\_\.]+))$/, + + // 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 ); + }; + }()), + + // Declare constructor + // Returns an instance object. + Popcorn = function( entity, options ) { + // Return new Popcorn object + return new Popcorn.p.init( entity, options || null ); + }; + + // Popcorn API version, automatically inserted via build system. + Popcorn.version = "@VERSION"; + + // Boolean flag allowing a client to determine if Popcorn can be supported + Popcorn.isSupported = true; + + // Instance caching + Popcorn.instances = []; + + // Declare a shortcut (Popcorn.p) to and a definition of + // the new prototype for our Popcorn constructor + Popcorn.p = Popcorn.prototype = { + + init: function( entity, options ) { + + var matches; + + // Supports Popcorn(function () { /../ }) + // Originally proposed by Daniel Brooks + + if ( typeof entity === "function" ) { + + // If document ready has already fired + if ( document.readyState === "interactive" || 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; + } + + // Check if entity is a valid string id + matches = rIdExp.exec( entity ); + + // Get media element by id or object reference + this.media = matches && matches.length && matches[ 2 ] ? + document.getElementById( matches[ 2 ] ) : + entity; + + // Create an audio or video element property reference + this[ ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video" ] = this.media; + + // Register new instance + Popcorn.instances.push( this ); + + this.options = options || {}; + + this.isDestroyed = false; + + this.data = { + + // Allows disabling a plugin per instance + disabled: [], + + // Stores DOM event queues by type + events: {}, + + // Stores Special event hooks data + hooks: {}, + + // Store track event history data + history: [], + + // Stores ad-hoc state related data] + state: { + volume: this.media.volume + }, + + // Store track event object references by trackId + trackRefs: {}, + + // Playback track event queues + trackEvents: { + byStart: [{ + + start: -1, + end: -1 + }], + byEnd: [{ + start: -1, + end: -1 + }], + animating: [], + startIndex: 0, + endIndex: 0, + previousUpdateTime: -1 + } + }; + + // Wrap true ready check + var isReady = function( that ) { + + var duration, videoDurationPlus, animate; + + if ( that.media.readyState >= 2 ) { + // Adding padding to the front and end of the arrays + // this is so we do not fall off either end + + duration = that.media.duration; + // Check for no duration info (NaN) + videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1; + + Popcorn.addTrackEvent( that, { + start: videoDurationPlus, + end: videoDurationPlus + }); + + if ( that.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) + animate = function () { + + Popcorn.timeUpdate( that, {} ); + + that.trigger( "timeupdate" ); + + requestAnimFrame( animate ); + }; + + requestAnimFrame( animate ); + + } else { + + that.data.timeUpdateFunction = function( event ) { + Popcorn.timeUpdate( that, event ); + }; + + if ( !that.isDestroyed ) { + that.media.addEventListener( "timeupdate", that.data.timeUpdateFunction, false ); + } + } + } else { + global.setTimeout(function() { + isReady( that ); + }, 1 ); + } + }; + + isReady( this ); + + return this; + } + }; + + // Extend constructor prototype to instance prototype + // Allows chaining methods to instances + Popcorn.p.init.prototype = Popcorn.p; + + Popcorn.forEach = function( obj, fn, context ) { + + if ( !obj || !fn ) { + return {}; + } + + context = context || this; + + var key, len; + + // Use native whenever possible + if ( forEach && obj.forEach === forEach ) { + return obj.forEach( fn, context ); + } + + if ( toString.call( obj ) === "[object NodeList]" ) { + for ( key = 0, len = obj.length; key < len; key++ ) { + fn.call( context, obj[ key ], key, obj ); + } + return obj; + } + + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + fn.call( context, obj[ key ], key, obj ); + } + } + return obj; + }; + + Popcorn.extend = function( obj ) { + var dest = obj, src = slice.call( arguments, 1 ); + + Popcorn.forEach( src, function( copy ) { + for ( var prop in copy ) { + dest[ prop ] = copy[ prop ]; + } + }); + + return dest; + }; + + + // A Few reusable utils, memoized onto Popcorn + Popcorn.extend( Popcorn, { + noConflict: function( deep ) { + + if ( deep ) { + global.Popcorn = _Popcorn; + } + + return Popcorn; + }, + error: function( msg ) { + throw new Error( msg ); + }, + guid: function( prefix ) { + Popcorn.guid.counter++; + return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter ); + }, + sizeOf: function( obj ) { + var size = 0; + + for ( var prop in obj ) { + size++; + } + + return size; + }, + isArray: Array.isArray || function( array ) { + return toString.call( array ) === "[object Array]"; + }, + + nop: function() {}, + + position: function( elem ) { + + var clientRect = elem.getBoundingClientRect(), + bounds = {}, + doc = elem.ownerDocument, + docElem = document.documentElement, + body = document.body, + clientTop, clientLeft, scrollTop, scrollLeft, top, left; + + // Determine correct clientTop/Left + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + + // Determine correct scrollTop/Left + scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop ); + scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft ); + + // Temp top/left + top = Math.ceil( clientRect.top + scrollTop - clientTop ); + left = Math.ceil( clientRect.left + scrollLeft - clientLeft ); + + for ( var p in clientRect ) { + bounds[ p ] = Math.round( clientRect[ p ] ); + } + + return Popcorn.extend({}, bounds, { top: top, left: left }); + }, + + disable: function( instance, plugin ) { + + var disabled = instance.data.disabled; + + if ( disabled.indexOf( plugin ) === -1 ) { + disabled.push( plugin ); + } + + return instance; + }, + enable: function( instance, plugin ) { + + var disabled = instance.data.disabled, + index = disabled.indexOf( plugin ); + + if ( index > -1 ) { + disabled.splice( index, 1 ); + } + + return instance; + }, + destroy: function( instance ) { + var events = instance.data.events, + singleEvent, item, fn; + + // Iterate through all events and remove them + for ( item in events ) { + singleEvent = events[ item ]; + for ( fn in singleEvent ) { + delete singleEvent[ fn ]; + } + events[ item ] = null; + } + + if ( !instance.isDestroyed ) { + instance.media.removeEventListener( "timeupdate", instance.data.timeUpdateFunction, false ); + instance.isDestroyed = true; + } + } + }); + + // Memoized GUID Counter + Popcorn.guid.counter = 1; + + // Factory to implement getters, setters and controllers + // as Popcorn instance methods. The IIFE will create and return + // an object with defined methods + Popcorn.extend(Popcorn.p, (function() { + + var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " + + "autoplay loop controls muted buffered readyState seeking paused played seekable ended", + ret = {}; + + + // Build methods, store in object that is returned and passed to extend + Popcorn.forEach( methods.split( /\s+/g ), function( name ) { + + ret[ name ] = function( arg ) { + + 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 ) { + + this.media[ name ] = arg; + + return this; + } + + return this.media[ name ]; + }; + }); + + return ret; + + })() + ); + + Popcorn.forEach( "enable disable".split(" "), function( method ) { + Popcorn.p[ method ] = function( plugin ) { + return Popcorn[ method ]( this, plugin ); + }; + }); + + Popcorn.extend(Popcorn.p, { + + // Rounded currentTime + roundTime: function() { + return -~this.media.currentTime; + }, + + // Attach an event to a single point in time + exec: function( time, fn ) { + + // Creating a one second track event with an empty end + Popcorn.addTrackEvent( this, { + start: time, + end: time + 1, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "exec" + } + }); + + 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.trigger( 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.indexOf( plugin ) > -1 ? "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 " + + "loadedmetadata loadeddata waiting playing canplay canplaythrough " + + "seeking seeked timeupdate ended ratechange durationchange volumechange" + }; + + Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " + + Popcorn.Events.MouseEvents + " " + + Popcorn.Events.Events; + + internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ]; + + // Privately compile events table at load time + (function( events, data ) { + + var apis = internal.events.apiTypes, + eventsList = events.Natives.split( /\s+/g ), + idx = 0, len = eventsList.length, prop; + + for( ; idx < len; idx++ ) { + data.hash[ eventsList[idx] ] = true; + } + + apis.forEach(function( val, idx ) { + + data.apis[ val ] = {}; + + var apiEvents = events[ val ].split( /\s+/g ), + len = apiEvents.length, + k = 0; + + for ( ; k < len; k++ ) { + data.apis[ val ][ apiEvents[ k ] ] = true; + } + }); + })( Popcorn.Events, internal.events ); + + Popcorn.events = { + + isNative: function( type ) { + return !!internal.events.hash[ type ]; + }, + getInterface: function( type ) { + + if ( !Popcorn.events.isNative( type ) ) { + return false; + } + + var eventApi = internal.events, + apis = eventApi.apiTypes, + apihash = eventApi.apis, + idx = 0, len = apis.length, api, tmp; + + for ( ; idx < len; idx++ ) { + tmp = apis[ idx ]; + + if ( apihash[ tmp ][ type ] ) { + api = tmp; + break; + } + } + return api; + }, + // Compile all native events to single array + all: Popcorn.Events.Natives.split( /\s+/g ), + // Defines all Event handling static functions + fn: { + trigger: function( type, data ) { + + var eventInterface, evt; + // setup checks for custom event system + if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) { + + eventInterface = Popcorn.events.getInterface( type ); + + if ( eventInterface ) { + + evt = document.createEvent( eventInterface ); + evt.initEvent( type, true, true, global, 1 ); + + this.media.dispatchEvent( evt ); + + return this; + } + + // Custom events + Popcorn.forEach( this.data.events[ type ], function( obj, key ) { + + obj.call( this, data ); + + }, this ); + + } + + return this; + }, + listen: function( type, fn ) { + + var self = this, + hasEvents = true, + eventHook = Popcorn.events.hooks[ type ], + origType = type, + tmp; + + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = {}; + hasEvents = false; + } + + // Check and setup event hooks + if ( eventHook ) { + + // Execute hook add method if defined + if ( eventHook.add ) { + eventHook.add.call( this, {}, fn ); + } + + // Reassign event type to our piggyback event type if defined + if ( eventHook.bind ) { + type = eventHook.bind; + } + + // Reassign handler if defined + if ( eventHook.handler ) { + tmp = fn; + + fn = function wrapper( event ) { + eventHook.handler.call( self, event, tmp ); + }; + } + + // assume the piggy back event is registered + hasEvents = true; + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = {}; + // Toggle if the previous assumption was untrue + hasEvents = false; + } + } + + // Register event and handler + this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn; + + // only attach one event of any type + if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { + + this.media.addEventListener( type, function( event ) { + + Popcorn.forEach( self.data.events[ type ], function( obj, key ) { + if ( typeof obj === "function" ) { + obj.call( self, event ); + } + }); + + }, false); + } + return this; + }, + unlisten: function( type, fn ) { + + if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) { + + delete this.data.events[ type ][ fn ]; + + return this; + } + + this.data.events[ type ] = null; + + return this; + } + }, + hooks: { + canplayall: { + bind: "canplaythrough", + add: function( event, callback ) { + + var state = false; + + if ( this.media.readyState ) { + + callback.call( this, event ); + + state = true; + } + + this.data.hooks.canplayall = { + fired: state + }; + }, + // declare special handling instructions + handler: function canplayall( event, callback ) { + + if ( !this.data.hooks.canplayall.fired ) { + // trigger original user callback once + callback.call( this, event ); + + this.data.hooks.canplayall.fired = true; + } + } + } + } + }; + + // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances + Popcorn.forEach( [ "trigger", "listen", "unlisten" ], function( key ) { + Popcorn.p[ key ] = Popcorn.events.fn[ key ]; + }); + + // Internal Only - Adds track events to the instance object + Popcorn.addTrackEvent = function( obj, track ) { + + // Determine if this track has default options set for it + // If so, apply them to the track object + if ( track && track._natives && track._natives.type && + ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) { + + track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track ); + } + + if ( track._natives ) { + // Supports user defined track event id + track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id; + + // Push track event ids into the history + obj.data.history.push( track._id ); + } + + track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate ); + track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate ); + + // Store this definition in an array sorted by times + var byStart = obj.data.trackEvents.byStart, + byEnd = obj.data.trackEvents.byEnd, + idx; + + for ( idx = byStart.length - 1; idx >= 0; idx-- ) { + + if ( track.start >= byStart[ idx ].start ) { + byStart.splice( idx + 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; + } + } + + this.timeUpdate( obj, null ); + + // Store references to user added trackevents in ref table + if ( track._id ) { + Popcorn.addTrackEvent.ref( obj, track ); + } + }; + + // Internal Only - Adds track event references to the instance object's trackRefs hash table + Popcorn.addTrackEvent.ref = function( obj, track ) { + obj.data.trackRefs[ track._id ] = track; + + return obj; + }; + + Popcorn.removeTrackEvent = function( obj, trackId ) { + + var historyLen = obj.data.history.length, + indexWasAt = 0, + byStart = [], + byEnd = [], + animating = [], + history = []; + + Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) { + // Preserve the original start/end trackEvents + if ( !o._id ) { + byStart.push( obj.data.trackEvents.byStart[i] ); + byEnd.push( obj.data.trackEvents.byEnd[i] ); + } + + // Filter for user track events (vs system track events) + if ( o._id ) { + + // Filter for the trackevent to remove + if ( o._id !== trackId ) { + byStart.push( obj.data.trackEvents.byStart[i] ); + byEnd.push( obj.data.trackEvents.byEnd[i] ); + } + + // Capture the position of the track being removed. + if ( o._id === trackId ) { + indexWasAt = i; + o._natives._teardown && o._natives._teardown.call( obj, o ); + } + } + + }); + + if ( obj.data.trackEvents.animating.length ) { + Popcorn.forEach( obj.data.trackEvents.animating, function( o, i, context ) { + // Preserve the original start/end trackEvents + if ( !o._id ) { + animating.push( obj.data.trackEvents.animating[i] ); + } + + // Filter for user track events (vs system track events) + if ( o._id ) { + // Filter for the trackevent to remove + if ( o._id !== trackId ) { + animating.push( obj.data.trackEvents.animating[i] ); + } + } + }); + } + + // Update + if ( indexWasAt <= obj.data.trackEvents.startIndex ) { + obj.data.trackEvents.startIndex--; + } + + if ( indexWasAt <= obj.data.trackEvents.endIndex ) { + obj.data.trackEvents.endIndex--; + } + + obj.data.trackEvents.byStart = byStart; + obj.data.trackEvents.byEnd = byEnd; + obj.data.trackEvents.animating = animating; + + for ( var i = 0; i < historyLen; i++ ) { + if ( obj.data.history[ i ] !== trackId ) { + history.push( obj.data.history[ i ] ); + } + } + + // Update ordered history array + obj.data.history = history; + + // Update track event references + Popcorn.removeTrackEvent.ref( obj, trackId ); + }; + + // Internal Only - Removes track event references from instance object's trackRefs hash table + Popcorn.removeTrackEvent.ref = function( obj, trackId ) { + delete obj.data.trackRefs[ trackId ]; + + 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, + animating = tracks.animating, + end = tracks.endIndex, + start = tracks.startIndex, + animIndex = 0, + + registryByName = Popcorn.registryByName, + + byEnd, byStart, byAnimate, natives, type; + + // 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; + natives.end.call( obj, event, 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 && + obj.data.disabled.indexOf( type ) === -1 ) { + + byStart._running = true; + natives.start.call( obj, event, byStart ); + + // If the `frameAnimation` option is used, + // push the current byStart object into the `animating` cue + if ( obj.options.frameAnimation && + ( byStart && byStart._running && byStart._natives.frame ) ) { + + animating.push( byStart ); + } + } + start++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + // If the `frameAnimation` option is used, iterate the animating track + // and execute the `frame` callback + if ( obj.options.frameAnimation ) { + while ( animIndex < animating.length ) { + + byAnimate = animating[ animIndex ]; + + if ( !byAnimate._running ) { + animating.splice( animIndex, 1 ); + } else { + byAnimate._natives.frame.call( obj, event, byAnimate, currentTime ); + animIndex++; + } + } + } + + // 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; + natives.end.call( obj, event, 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 && + obj.data.disabled.indexOf( type ) === -1 ) { + + byEnd._running = true; + natives.start.call( obj, event, byEnd ); + + // If the `frameAnimation` option is used, + // push the current byEnd object into the `animating` cue + if ( obj.options.frameAnimation && + ( byEnd && byEnd._running && byEnd._natives.frame ) ) { + + animating.push( byEnd ); + } + } + end--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + + // If the `frameAnimation` option is used, iterate the animating track + // and execute the `frame` callback + if ( obj.options.frameAnimation ) { + while ( animIndex < animating.length ) { + + byAnimate = animating[ animIndex ]; + + if ( !byAnimate._running ) { + animating.splice( animIndex, 1 ); + } else { + byAnimate._natives.frame.call( obj, event, byAnimate, currentTime ); + animIndex++; + } + } + } + // time bar is not moving ( video is paused ) + } + + tracks.endIndex = end; + tracks.startIndex = start; + tracks.previousUpdateTime = currentTime; + }; + + // Map and Extend TrackEvent functions to all Popcorn instances + Popcorn.extend( Popcorn.p, { + + getTrackEvents: function() { + return Popcorn.getTrackEvents.call( null, this ); + }, + + getTrackEvent: function( id ) { + return Popcorn.getTrackEvent.call( null, this, id ); + }, + + getLastTrackEventId: function() { + return Popcorn.getLastTrackEventId.call( null, this ); + }, + + removeTrackEvent: function( id ) { + + Popcorn.removeTrackEvent.call( null, this, id ); + return this; + }, + + removePlugin: function( name ) { + Popcorn.removePlugin.call( null, this, name ); + return this; + }, + + timeUpdate: function( event ) { + Popcorn.timeUpdate.call( null, this, event ); + return this; + }, + + destroy: function() { + Popcorn.destroy.call( null, this ); + return this; + } + }); + + // Plugin manifests + Popcorn.manifest = {}; + // Plugins are registered + Popcorn.registry = []; + Popcorn.registryByName = {}; + // An interface for extending Popcorn + // with plugin functionality + Popcorn.plugin = function( name, definition, manifest ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + var reserved = [ "start", "end" ], + plugin = {}, + setup, + isfn = typeof definition === "function", + methods = [ "_setup", "_teardown", "start", "end", "frame" ]; + + // combines calls of two function calls into one + var combineFn = function( first, second ) { + + first = first || Popcorn.nop; + second = second || Popcorn.nop; + + return function() { + first.apply( this, arguments ); + second.apply( this, arguments ); + }; + }; + + // If `manifest` arg is undefined, check for manifest within the `definition` object + // If no `definition.manifest`, an empty object is a sufficient fallback + Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; + + // apply safe, and empty default functions + methods.forEach(function( method ) { + definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name ); + }); + + var pluginFn = function( setup, options ) { + + if ( !options ) { + return this; + } + + // Storing the plugin natives + var natives = options._natives = {}, + compose = "", + defaults, originalOpts, manifestOpts, mergedSetupOpts; + + Popcorn.extend( natives, setup ); + + options._natives.type = name; + options._running = false; + + natives.start = natives.start || natives[ "in" ]; + natives.end = natives.end || natives[ "out" ]; + + // Check for previously set default options + defaults = this.options.defaults && this.options.defaults[ options._natives && options._natives.type ]; + + // default to an empty string if no effect exists + // split string into an array of effects + options.compose = options.compose && options.compose.split( " " ) || []; + options.effect = options.effect && options.effect.split( " " ) || []; + + // join the two arrays together + options.compose = options.compose.concat( options.effect ); + + options.compose.forEach(function( composeOption ) { + + // if the requested compose is garbage, throw it away + compose = Popcorn.compositions[ composeOption ] || {}; + + // extends previous functions with compose function + methods.forEach(function( method ) { + natives[ method ] = combineFn( natives[ method ], compose[ method ] ); + }); + }); + + // Ensure a manifest object, an empty object is a sufficient fallback + options._natives.manifest = manifest; + + // Checks for expected properties + if ( !( "start" in options ) ) { + options.start = options[ "in" ] || 0; + } + + if ( !( "end" in options ) ) { + options.end = options[ "out" ] || this.duration() || Number.MAX_VALUE; + } + + // Merge with defaults if they exist, make sure per call is prioritized + mergedSetupOpts = defaults ? Popcorn.extend( {}, defaults, options ) : + options; + + // Resolves 239, 241, 242 + if ( !mergedSetupOpts.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; + + mergedSetupOpts.target = manifestOpts && "target" in manifestOpts && manifestOpts.target; + } + + // Trigger _setup method if exists + options._natives._setup && options._natives._setup.call( this, mergedSetupOpts ); + + // Create new track event for this instance + Popcorn.addTrackEvent( this, Popcorn.extend( mergedSetupOpts, options ) ); + + // Future support for plugin event definitions + // for all of the native events + Popcorn.forEach( setup, function( callback, type ) { + + if ( type !== "type" ) { + + if ( reserved.indexOf( type ) === -1 ) { + + this.listen( type, callback ); + } + } + + }, this ); + + return this; + }; + + // Assign new named definition + plugin[ name ] = function( options ) { + return pluginFn.call( this, isfn ? definition.call( this, options ) : definition, + options ); + }; + + // Extend Popcorn.p with new named definition + Popcorn.extend( Popcorn.p, plugin ); + + // 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.trigger( "error", Popcorn.plugin.errors ); + } + }; + } + + // Debug-mode flag for plugin development + Popcorn.plugin.debug = false; + + // 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 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 ) && + ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) { + + byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] ); + + byStart.splice( idx, 1 ); + byEnd.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--; + } + } + } + + //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; + + // 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 ) { + + if ( !filename ) { + return this; + } + + var that = this; + + Popcorn.xhr({ + url: filename, + dataType: type, + success: function( data ) { + + var tracksObject = definition( data ), + 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.player = function( name, player ) { + + player = player || {}; + + var playerFn = function( target, src, options ) { + + options = options || {}; + + // List of events + var date = new Date() / 1000, + baselineTime = date, + currentTime = 0, + volume = 1, + muted = false, + events = {}, + + // The container div of the resource + container = document.getElementById( rIdExp.exec( target ) && rIdExp.exec( target )[ 2 ] ) || + document.getElementById( target ) || + target, + basePlayer = {}, + timeout, + popcorn; + + // copies a div into the media object + for( var val in container ) { + + if ( typeof container[ val ] === "object" ) { + + basePlayer[ val ] = container[ val ]; + } else if ( typeof container[ val ] === "function" ) { + + basePlayer[ val ] = (function( value ) { + + 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 + }); + + // Adds an event listener to the object + basePlayer.addEventListener = function( evtName, fn ) { + + if ( !events[ evtName ] ) { + + events[ evtName ] = []; + } + + events[ evtName ].push( fn ); + 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 ); + } + } + + Popcorn.forEach( events[ eventName ], function( val ) { + + val.call( self, evt, self ); + }); + }; + + // Attempt to get src from playerFn parameter + basePlayer.src = src || ""; + basePlayer.readyState = 0; + basePlayer.duration = 0; + basePlayer.paused = true; + basePlayer.ended = 0; + + 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( "load" ); + basePlayer.dispatchEvent( "loadeddata" ); + } + + // when a custom player is loaded, load basePlayer state into custom player + basePlayer.addEventListener( "load", function() { + + // if a player is not ready before currentTime is called, this will set it after it is ready + basePlayer.currentTime = currentTime; + + // same as above with volume and muted + basePlayer.volume = volume; + basePlayer.muted = muted; + }); + + basePlayer.addEventListener( "loadeddata", function() { + + // if play was called before player ready, start playing video + !basePlayer.paused && basePlayer.play(); + }); + + popcorn = new Popcorn.p.init( basePlayer, options ); + + return popcorn; + }; + + Popcorn[ name ] = Popcorn[ name ] || playerFn; + }; + + Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { + + object.__defineGetter__( description, options.get || Popcorn.nop ); + object.__defineSetter__( description, options.set || Popcorn.nop ); + }; + + // Cache references to reused RegExps + var rparams = /\?/, + // XHR Setup object + setup = { + url: "", + data: "", + dataType: "", + success: Popcorn.nop, + type: "GET", + async: true, + xhr: function() { + return new global.XMLHttpRequest(); + } + }; + + Popcorn.xhr = function( options ) { + + options.dataType = options.dataType && options.dataType.toLowerCase() || null; + + if ( options.dataType && + ( options.dataType === "jsonp" || options.dataType === "script" ) ) { + + Popcorn.xhr.getJSONP( + options.url, + options.success, + options.dataType === "script" + ); + return; + } + + var settings = Popcorn.extend( {}, setup, options ); + + // Create new XMLHttpRequest object + settings.ajax = settings.xhr(); + + if ( settings.ajax ) { + + if ( settings.type === "GET" && settings.data ) { + + // append query string + settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data; + + // Garbage collect and reset settings.data + settings.data = null; + } + + + settings.ajax.open( settings.type, settings.url, settings.async ); + settings.ajax.send( settings.data || null ); + + return Popcorn.xhr.httpData( settings ); + } + }; + + + Popcorn.xhr.httpData = function( settings ) { + + var data, json = null; + + 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 + }; + + // If a dataType was specified, return that type of data + if ( settings.dataType ) { + data = data[ settings.dataType ]; + } + + + settings.success.call( settings.ajax, data ); + + } + }; + return data; + }; + + Popcorn.xhr.getJSONP = function( url, success, isScript ) { + + var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement, + script = document.createElement( "script" ), + paramStr = url.split( "?" )[ 1 ], + isFired = false, + params = [], + callback, parts, callparam; + + if ( paramStr && !isScript ) { + params = paramStr.split( "&" ); + } + + if ( params.length ) { + parts = params[ params.length - 1 ].split( "=" ); + } + + callback = params.length ? ( parts[ 1 ] ? parts[ 1 ] : parts[ 0 ] ) : "jsonp"; + + if ( !paramStr && !isScript ) { + url += "?callback=" + callback; + } + + if ( callback && !isScript ) { + + // If a callback name already exists + if ( !!window[ callback ] ) { + // Create a new unique callback name + callback = Popcorn.guid( callback ); + } + + // Define the JSONP success callback globally + window[ callback ] = function( data ) { + // Fire success callbacks + success && success( data ); + isFired = true; + }; + + // Replace callback param and callback name + url = url.replace( parts.join( "=" ), parts[ 0 ] + "=" + callback ); + } + + script.onload = 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 ); + }; + + 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 ]; + } + }; + + + // 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( global.navigator.userLanguage || global.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 ); + } + } + } + }; + + // alias for exec function + Popcorn.p.cue = Popcorn.p.exec; + + function getItems() { + + var item, + list = []; + + if ( Object.keys ) { + list = Object.keys( Popcorn.p ); + } else { + + for ( item in Popcorn.p ) { + if ( hasOwn.call( Popcorn.p, item ) ) { + list.push( item ); + } + } + } + + return list.join( "," ).toLowerCase().split( ","); + } + + // Protected API methods + Popcorn.protect = { + natives: getItems() + }; + + // Exposes Popcorn to global context + global.Popcorn = Popcorn; + +})(window, window.document); diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/libs/popcorn.jwplayer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/popcorn.jwplayer.js Wed Dec 07 12:07:12 2011 +0100 @@ -0,0 +1,111 @@ +var jwplayerObjects = {}; + +Popcorn.player( "jwplayer", { + _setup: function( options ) { + + var media = this, + player = {}, + container = document.createElement( "div" ), + currentTime = 0, + seekTime = 0, + seeking = false, + dataLoaded = false; + container.id = media.id + Popcorn.guid(); + + media.appendChild( container ); + + var initApi = function () { + jwplayer( container.id ).onTime(function() { + currentTime = jwplayer(container.id).getPosition(); + media.dispatchEvent( "timeupdate" ); + // timeout = setTimeout( timeupdate, 10 ); + }); + + media.play = function() { + media.paused = false; + media.dispatchEvent( "play" ); + + media.dispatchEvent( "playing" ); + jwplayer( container.id ).play(); + }; + + media.pause = function() { + + if ( !media.paused ) { + media.paused = true; + media.dispatchEvent( "pause" ); + jwplayer( container.id ).pause(); + } + }; + + Popcorn.player.defineProperty( media, "currentTime", { + set: function( val ) { + // make sure val is a number + currentTime = seekTime = +val; + seeking = true; + media.dispatchEvent( "seeked" ); + media.dispatchEvent( "timeupdate" ); + jwplayer( container.id ).seek( currentTime ); + return currentTime; + }, + get: function() { + return jwplayer( container.id ).getPosition(); + } + }); + + Popcorn.player.defineProperty( media, "muted", { + set: function( val ) { + if ( jwplayer( container.id ).getMute() !== val ) { + if ( val ) { + jwplayer( container.id ).setMute(true); + } else { + jwplayer( container.id ).setMute(false); + } + + media.dispatchEvent( "volumechange" ); + } + + return jwplayer( container.id ).getMute(); + }, + get: function() { + return jwplayer( container.id ).getMute(); + } + }); + + Popcorn.player.defineProperty( media, "volume", { + + set: function( val ) { + + if ( jwplayer( container.id ).getVolume() !== val *100 ) { + jwplayer( container.id ).setVolume( val * 100); + media.dispatchEvent( "volumechange" ); + } + + return (jwplayer( container.id ).getVolume()) / 100; + }, + + get: function() { + return jwplayer( container.id ).getVolume() / 100; + } + }); + + media.readyState = 4; + media.dispatchEvent( 'load' ); + dataLoaded = true; + + media.duration = options.duration; + media.dispatchEvent( 'durationchange' ); + + media.paused && media.dispatchEvent( 'loadeddata' ); + + }; + + options.events = { + onReady: initApi + }; + + jwplayer( container.id ).setup(options); + + } +}); + diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/libs/popcorn.mediafragment.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/popcorn.mediafragment.js Wed Dec 07 12:07:12 2011 +0100 @@ -0,0 +1,58 @@ +// PLUGIN: Mediafragment + +(function ( Popcorn ) { + + /** + * Mediafragment popcorn plug-in + * Adds (limited) support for mediafragment requests + * to a popcorn video. + * @param {Object} options + * + **/ + Popcorn.plugin( "mediafragment" , { + + manifest: { + about: { + name: "Popcorn mediafragment plugin", + version: "0.1", + author: "Karim Hamidou", + website: "http://neyret.fr/~karim" + }, + options: { + } + }, + + _setup: function( options ) { + var advanceTime = function() { + var url = window.location.href; + + if ( url.split( "#" )[ 1 ] != null ) { + pageoffset = url.split( "#" )[1]; + + if ( pageoffset.substring( 2 ) != null ) { + var offsettime = pageoffset.substring( 2 ); + this.currentTime( parseFloat( offsettime ) ); + } + } + } + + var updateTime = function() { + var history = window.history; + if ( !history.pushState ) { + return false; + } + + splitArr = window.location.href.split( "#" ) + history.replaceState( {}, "", splitArr[0] + "#t=" + this.currentTime().toFixed( 2 ) ); + }; + + this.listen( "loadedmetadata", advanceTime ); + this.listen( "pause", updateTime ); + this.listen( "seeked", updateTime ); + }, + + _teardown: function( options ) { + // FIXME: anything to implement here ? + } + }); +})( Popcorn ); diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/libs/popcorn.youtube.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/popcorn.youtube.js Wed Dec 07 12:07:12 2011 +0100 @@ -0,0 +1,220 @@ +// A global callback for youtube... that makes me angry +var onYouTubePlayerReady = function( containerId ) { + + onYouTubePlayerReady[ containerId ] && onYouTubePlayerReady[ containerId ](); +}; +onYouTubePlayerReady.stateChangeEventHandler = {}; + +Popcorn.player( "youtube", { + _setup: function( options ) { + + var media = this, + youtubeObject, + container = document.createElement( "div" ), + currentTime = 0, + seekTime = 0, + seeking = false, + + // state code for volume changed polling + volumeChanged = false, + lastMuted = false, + lastVolume = 0; + + container.id = media.id + Popcorn.guid(); + + media.appendChild( container ); + + var youtubeInit = function() { + + var flashvars, + params, + attributes, + src; + + // expose a callback to this scope, that is called from the global callback youtube calls + onYouTubePlayerReady[ container.id ] = function() { + + youtubeObject = document.getElementById( container.id ); + + // more youtube callback nonsense + onYouTubePlayerReady.stateChangeEventHandler[ container.id ] = function( state ) { + + // playing is state 1 + // paused is state 2 + if ( state === 1 ) { + + media.paused && media.play(); + // youtube fires paused events while seeking + // this is the only way to get seeking events + } else if ( state === 2 ) { + + // silly logic forced on me by the youtube API + // calling youtube.seekTo triggers multiple events + // with the second events getCurrentTime being the old time + if ( seeking && seekTime === currentTime && seekTime !== youtubeObject.getCurrentTime() ) { + + seeking = false; + youtubeObject.seekTo( currentTime ); + return; + } + + currentTime = youtubeObject.getCurrentTime(); + media.dispatchEvent( "timeupdate" ); + !media.paused && media.pause(); + } + }; + + // youtube requires callbacks to be a string to a function path from the global scope + youtubeObject.addEventListener( "onStateChange", "onYouTubePlayerReady.stateChangeEventHandler." + container.id ); + + var timeupdate = function() { + + if ( !media.paused ) { + + currentTime = youtubeObject.getCurrentTime(); + media.dispatchEvent( "timeupdate" ); + setTimeout( timeupdate, 10 ); + } + }; + + var volumeupdate = function() { + + if ( lastMuted !== youtubeObject.isMuted() ) { + + lastMuted = youtubeObject.isMuted(); + media.dispatchEvent( "volumechange" ); + } + + if ( lastVolume !== youtubeObject.getVolume() ) { + + lastVolume = youtubeObject.getVolume(); + media.dispatchEvent( "volumechange" ); + } + + setTimeout( volumeupdate, 250 ); + }; + + media.play = function() { + + media.paused = false; + media.dispatchEvent( "play" ); + + media.dispatchEvent( "playing" ); + timeupdate(); + youtubeObject.playVideo(); + }; + + media.pause = function() { + + if ( !media.paused ) { + + media.paused = true; + media.dispatchEvent( "pause" ); + youtubeObject.pauseVideo(); + } + }; + + Popcorn.player.defineProperty( media, "currentTime", { + set: function( val ) { + + // make sure val is a number + currentTime = seekTime = +val; + seeking = true; + media.dispatchEvent( "seeked" ); + media.dispatchEvent( "timeupdate" ); + youtubeObject.seekTo( currentTime ); + return currentTime; + }, + get: function() { + + return currentTime; + } + }); + + Popcorn.player.defineProperty( media, "muted", { + set: function( val ) { + + if ( youtubeObject.isMuted() !== val ) { + + if ( val ) { + + youtubeObject.mute(); + } else { + + youtubeObject.unMute(); + } + + lastMuted = youtubeObject.isMuted(); + media.dispatchEvent( "volumechange" ); + } + + return youtubeObject.isMuted(); + }, + get: function() { + + return youtubeObject.isMuted(); + } + }); + + Popcorn.player.defineProperty( media, "volume", { + set: function( val ) { + + if ( youtubeObject.getVolume() !== val ) { + + youtubeObject.setVolume( val ); + lastVolume = youtubeObject.getVolume(); + media.dispatchEvent( "volumechange" ); + } + + return youtubeObject.getVolume(); + }, + get: function() { + + return youtubeObject.getVolume(); + } + }); + + media.readyState = 4; + media.dispatchEvent( "load" ); + media.duration = youtubeObject.getDuration(); + media.dispatchEvent( "durationchange" ); + volumeupdate(); + + media.dispatchEvent( "loadeddata" ); + }; + + options.controls = +options.controls === 0 || +options.controls === 1 ? options.controls : 1; + options.annotations = +options.annotations === 1 || +options.annotations === 3 ? options.annotations : 1; + + flashvars = { + playerapiid: container.id, + controls: options.controls, + iv_load_policy: options.annotations + }; + + params = { + wmode: "transparent", + allowScriptAccess: "always" + }; + + attributes = { + id: container.id + }; + + src = /^.*[\/=](.{11})/.exec( media.src )[ 1 ]; + + swfobject.embedSWF( "http://www.youtube.com/e/" + src + "?enablejsapi=1&playerapiid=" + container.id + "&version=3", + container.id, media.offsetWidth, media.offsetHeight, "8", null, + flashvars, params, attributes ); + }; + + if ( !window.swfobject ) { + + Popcorn.getScript( "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", youtubeInit ); + } else { + + youtubeInit(); + } + } +}); + diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/serializers/JSONSerializer.js --- a/src/js/serializers/JSONSerializer.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/serializers/JSONSerializer.js Wed Dec 07 12:07:12 2011 +0100 @@ -39,9 +39,9 @@ /* this function searches for an annotation which matches title, description and keyword "" matches any field. + Note: it ignores tweets. */ IriSP.JSONSerializer.prototype.searchAnnotations = function(title, description, keyword) { - /* we can have many types of annotations. We want search to only look for regular segments */ /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either null or undefined. @@ -57,10 +57,61 @@ searchViewType = view.annotation_types[0]; } + var filterfn = function(annotation) { + if( searchViewType != "" && + typeof(annotation.meta) !== "undefined" && + typeof(annotation.meta["id-ref"]) !== "undefined" && + annotation.meta["id-ref"] !== searchViewType) { + return true; // don't pass + } else { + return false; + } + }; + + return this.searchAnnotationsFilter(title, description, keyword, filterfn); + +}; + +/* only look for tweets */ +IriSP.JSONSerializer.prototype.searchTweets = function(title, description, keyword) { + /* we can have many types of annotations. We want search to only look for regular segments */ + /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either + null or undefined. + */ + var view; + + if (typeof(this._data.views) !== "undefined" && this._data.views !== null) + view = this._data.views[0]; + + var searchViewType = ""; + + if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { + searchViewType = view.annotation_types[0]; + } + + var filterfn = function(annotation) { + if( searchViewType != "" && + typeof(annotation.meta) !== "undefined" && + typeof(annotation.meta["id-ref"]) !== "undefined" && + annotation.meta["id-ref"] !== searchViewType) { + return false; // pass + } else { + return true; + } + }; + + return this.searchAnnotationsFilter(title, description, keyword, filterfn); + +}; + +/* + the previous function call this one, which is more general: + */ +IriSP.JSONSerializer.prototype.searchAnnotationsFilter = function(title, description, keyword, filter) { + var rTitle; var rDescription; var rKeyword; - /* match anything if given the empty string */ if (title == "") title = ".*"; @@ -80,11 +131,10 @@ var annotation = this._data.annotations[i]; /* filter the annotations whose type is not the one we want */ - if (searchViewType != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" - && annotation.meta["id-ref"] !== searchViewType) { + if (filter(annotation)) { continue; } - + if (rTitle.test(annotation.content.title) && rDescription.test(annotation.content.description)) { /* FIXME : implement keyword support */ @@ -128,6 +178,39 @@ return ret; }; +/* breaks a string in words and searches each of these words. Returns an array + of objects with the id of the annotation and its number of occurences. + + FIXME: optimize ? seems to be n^2 in the worst case. +*/ +IriSP.JSONSerializer.prototype.searchTweetsOccurences = function(searchString) { + var ret = { }; + var keywords = searchString.split(/\s+/); + + for (var i in keywords) { + var keyword = keywords[i]; + + // search this keyword in descriptions and title + var found_annotations = [] + found_annotations = found_annotations.concat(this.searchTweets(keyword, "", "")); + found_annotations = found_annotations.concat(this.searchTweets("", keyword, "")); + + for (var j in found_annotations) { + var current_annotation = found_annotations[j]; + + if (!ret.hasOwnProperty(current_annotation.id)) { + ret[current_annotation.id] = 1; + } else { + ret[current_annotation.id] += 1; + } + + } + + }; + + return ret; +}; + /* takes the currentTime and returns all the annotations that are displayable at the moment NB: only takes account the first type of annotations - ignores tweets currentTime is in seconds. diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/site.js --- a/src/js/site.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/site.js Wed Dec 07 12:07:12 2011 +0100 @@ -31,7 +31,10 @@ } }; +IriSP.paths = { + "imgs": "./../../src/css/imgs" +}; IriSP.default_templates_vars = { - "img_dir" : "./../../src/css/imgs" + "img_dir" : IriSP.paths.imgs }; diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/widgets/playerWidget.js --- a/src/js/widgets/playerWidget.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/widgets/playerWidget.js Wed Dec 07 12:07:12 2011 +0100 @@ -24,8 +24,8 @@ this._Popcorn.listen("play", IriSP.wrap(this, this.playButtonUpdater)); this._Popcorn.listen("pause", IriSP.wrap(this, this.playButtonUpdater)); this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeDisplayUpdater)); - this._Popcorn.listen("IriSP.SegmentsWidget.matchFound", IriSP.wrap(this, this.searchMatch)); - this._Popcorn.listen("IriSP.SegmentsWidget.noMatchFound", IriSP.wrap(this, this.searchNoMatch)); + this._Popcorn.listen("IriSP.search.matchFound", IriSP.wrap(this, this.searchMatch)); + this._Popcorn.listen("IriSP.search.noMatchFound", IriSP.wrap(this, this.searchNoMatch)); this.selector.find(".Ldt-CtrlPlay").click(function() { self.playHandler.call(self); }); @@ -76,11 +76,20 @@ var status = this._Popcorn.media.paused; if ( status == true ){ - this.selector.find(".ui-icon-play").css( "background-position", "-16px -160px" ); this.selector.find(".Ldt-CtrlPlay").attr("title", "Play"); + + // we use templToHTML because it has some predefined + // vars like where to get the images + var templ = IriSP.templToHTML("url({{img_dir}}/pause_sprite.png)"); + this.selector.find(".Ldt-CtrlPlay").css("background-image", templ); + } else { - this.selector.find(".ui-icon-play").css( "background-position","0px -160px" ); this.selector.find(".Ldt-CtrlPlay").attr("title", "Pause"); + + // we use templToHTML because it has some predefined + // vars like where to get the images + var templ = IriSP.templToHTML("url({{img_dir}}/play_sprite.png)"); + this.selector.find(".Ldt-CtrlPlay").css("background-image", templ); } }; @@ -124,18 +133,25 @@ this._searchBlockOpen = true; this.selector.find(".LdtSearchInput").bind('keyup', null, function() { self.searchHandler.call(self); } ); + // we need this variable because some widget can find a match in + // their data while at the same time other's don't. As we want the + // search field to become green when there's a match, we need a + // variable to remember that we had one. + this._positiveMatch = false; + // tell the world the field is open this._Popcorn.trigger("IriSP.search.open"); } else { this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); this.selector.find(".LdtSearchInput").attr('value',''); - this.selector.find(".ui-icon-search").css("background-position","-160px -112px"); this.selector.find(".LdtSearch").hide(100); // unbind the watcher event. this.selector.find(".LdtSearchInput").unbind('keypress set'); this._searchBlockOpen = false; + + this._positiveMatch = false; this._Popcorn.trigger("IriSP.search.closed"); } @@ -145,6 +161,7 @@ field changes */ IriSP.PlayerWidget.prototype.searchHandler = function() { this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); + this._positiveMatch = false; // do nothing if the search field is empty, instead of highlighting everything. if (this._searchLastValue == "") { @@ -160,11 +177,13 @@ highlight a match. */ IriSP.PlayerWidget.prototype.searchMatch = function() { + this._positiveMatch = true; this.selector.find(".LdtSearchInput").css('background-color','#e1ffe1'); } /* the same, except that no value could be found */ IriSP.PlayerWidget.prototype.searchNoMatch = function() { - this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a"); + if (this._positiveMatch !== true) + this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a"); } diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/widgets/polemicWidget.js --- a/src/js/widgets/polemicWidget.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/widgets/polemicWidget.js Wed Dec 07 12:07:12 2011 +0100 @@ -31,10 +31,18 @@ this.PaperSlider; this.heightOfChart; this.tweets = new Array(); - this.svgElements = new Array(); + this.svgElements = {}; // Make and define the Raphael area this.paper = Raphael(document.getElementById(this._id), config.width, config.height); + + this.oldSearchMatches = []; + + // event handlers + this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { this.searchHandler(searchString); })); + this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.searchFieldClosedHandler)); + this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.searchFieldClearedHandler)); + }; IriSP.PolemicWidget.prototype = new IriSP.Widget(); @@ -269,14 +277,16 @@ var e = this.paper.rect(x, y, frameSize - margin, TweetHeight /* height */) .attr({stroke:"#00","stroke-width":0.1, fill: colors[j]}); - this.svgElements.push(e); addEheight += TweetHeight; + e.color = colors[j]; e.time = frames[i].mytweetsID[k].timeframe; e.title = frames[i].mytweetsID[k].title; e.id = frames[i].mytweetsID[k].cinecast_id; + this.svgElements[e.id] = e; + e.mouseover(function(element) { return function (event) { // event.clientX and event.clientY are to raphael what event.pageX and pageY are to jquery. self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), event.clientX - 106, event.clientY - 160); @@ -341,4 +351,55 @@ this.sliderTip.attr("x", time * (this.width / (duration / 1000))); }; - +IriSP.PolemicWidget.prototype.searchHandler = function(searchString) { + + if (searchString == "") + return; + + var matches = this._serializer.searchTweetsOccurences(searchString); + + if (IriSP.countProperties(matches) > 0) { + this._Popcorn.trigger("IriSP.search.matchFound"); + } else { + this._Popcorn.trigger("IriSP.search.noMatchFound"); + } + + for (var id in matches) { + var factor = 0.5 + matches[id] * 0.2; + if (this.svgElements.hasOwnProperty(id)) { + this.svgElements[id].attr({fill: "#fc00ff"}); + } + } + + // clean up the blocks that were in the previous search + // but who aren't in the current one. + for (var id in this.oldSearchMatches) { + if (!matches.hasOwnProperty(id)) { + var e = this.svgElements[id]; + e.attr({fill: e.color}); + } + } + + this.oldSearchMatches = matches; +}; + +IriSP.PolemicWidget.prototype.searchFieldClearedHandler = function() { + // clean up the blocks that were in the previous search + // but who aren't in the current one. + for (var id in this.oldSearchMatches) { + var e = this.svgElements[id]; + e.attr({fill: e.color}); + } + +}; + +IriSP.PolemicWidget.prototype.searchFieldClosedHandler = function() { + // clean up the blocks that were in the previous search + // but who aren't in the current one. + for (var id in this.oldSearchMatches) { + var e = this.svgElements[id]; + e.attr({fill: e.color}); + } + +}; + diff -r bae7c50704d7 -r 06d2aa32c5f4 src/js/widgets/segmentsWidget.js --- a/src/js/widgets/segmentsWidget.js Wed Dec 07 11:51:04 2011 +0100 +++ b/src/js/widgets/segmentsWidget.js Wed Dec 07 12:07:12 2011 +0100 @@ -118,13 +118,7 @@ /* restores the view after a search */ IriSP.SegmentsWidget.prototype.clear = function() { - // reinit the fields - for (var id in this.oldSearchMatches) { - - IriSP.jQuery("#"+id).dequeue(); - IriSP.jQuery("#"+id).css('border','none'); - IriSP.jQuery("#"+id).animate({opacity:0.3}, 100); - } + this.selector.children(".Ldt-iri-chapter").css('border','none').animate({opacity:0.3}, 100); }; IriSP.SegmentsWidget.prototype.clickHandler = function(annotation) { @@ -140,11 +134,15 @@ var matches = this._serializer.searchOccurences(searchString); if (IriSP.countProperties(matches) > 0) { - this._Popcorn.trigger("IriSP.SegmentsWidget.matchFound"); + this._Popcorn.trigger("IriSP.search.matchFound"); } else { - this._Popcorn.trigger("IriSP.SegmentsWidget.noMatchFound"); + this._Popcorn.trigger("IriSP.search.noMatchFound"); } + // un-highlight all the blocks + this.selector.children(".Ldt-iri-chapter").css("opacity", 0.1); + + // then highlight the ones with matches. for (var id in matches) { var factor = 0.5 + matches[id] * 0.2; this.selector.find("#"+id).dequeue(); @@ -152,15 +150,7 @@ this.selector.find("#"+id).animate({opacity:factor}, 200); } - // clean up the blocks that were in the previous search - // but who aren't in the current one. - for (var id in this.oldSearchMatches) { - if (!matches.hasOwnProperty(id)) { - IriSP.jQuery("#"+id).dequeue(); - IriSP.jQuery("#"+id).animate({opacity:0.3}, 200); - } - } - + this.oldSearchMatches = matches; };