diff --git a/popcorn.js b/popcorn.js index 36c8448f3..a66724401 100644 --- a/popcorn.js +++ b/popcorn.js @@ -547,6 +547,7 @@ // Attach an event to a single point in time exec: function( id, time, fn ) { var length = arguments.length, + currentTime = this.media.currentTime + 1, trackEvent, sec; // Check if first could possibly be a SMPTE string @@ -574,33 +575,68 @@ } else { // Support for new forms - // Get the trackEvent that matches the given id. - trackEvent = this.getTrackEvent( id ); + // 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 = currentTime; + + } else { - if ( trackEvent ) { - // p.cue( "my-id", 12 ); - // p.cue( "my-id", function() { ... }); - if ( typeof id === "string" && length === 2 ) { + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + if ( trackEvent ) { // 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() { ... }); + 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 { - // 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; + 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 ); + if ( typeof time === "number" ) { + fn = Popcorn.nop(); + } + + // p.cue( "c", function() {}); + if ( typeof time === "function" ) { + fn = time; + time = currentTime; + } } } } } // Creating a one second track event with an empty end + // Or update an existing track event with new values Popcorn.addTrackEvent( this, { id: id, start: time, @@ -903,7 +939,7 @@ // Internal Only - Adds track events to the instance object Popcorn.addTrackEvent = function( obj, track ) { - var trackEvent; + var trackEvent, isUpdate, eventType; // Do a lookup for existing trackevents with this id if ( track.id ) { @@ -912,6 +948,7 @@ // If a track event by this id currently exists, modify it if ( trackEvent ) { + isUpdate = true; // Create a new object with the existing trackEvent // Extend with new track properties track = Popcorn.extend( {}, trackEvent, track ); @@ -992,6 +1029,33 @@ if ( track._id ) { Popcorn.addTrackEvent.ref( obj, track ); } + + // If the call to addTrackEvent was an update/modify call, fire an event + if ( isUpdate ) { + + // Determine appropriate event type to trigger + // they are identical in function, but the naming + // adds some level of intuition for the end developer + // to rely on + if ( track._natives.type === "cue" ) { + eventType = "cuechange"; + } else { + eventType = "trackchange"; + } + + // Fire an event with change information + obj.emit( eventType, { + id: track.id, + previousValue: { + time: trackEvent.start, + fn: trackEvent._natives.start + }, + currentValue: { + time: track.start, + fn: track._natives.start + } + }); + } }; // Internal Only - Adds track event references to the instance object's trackRefs hash table diff --git a/test/popcorn.unit.js b/test/popcorn.unit.js index 93ee897d8..8a3f79b30 100644 --- a/test/popcorn.unit.js +++ b/test/popcorn.unit.js @@ -4323,6 +4323,78 @@ asyncTest( "Plug-ins with a `once` attribute should be removed after `end` is fi $pop.play( 0 ); }); + +module( "Popcorn Cue/Track" ); +asyncTest( "Cue API", 12, function() { + var p = Popcorn( "#video" ); + + p.on( "canplayall", function() { + + // Declare a cue: schedule a function to execute at a time. + p.cue( 10, function() {}); + + equal( p.data.trackEvents.byStart.length, 3, "Declare a cue: schedule a function to execute at a time., p.cue( 10, function() {});" ); + + + // Declare a cue: unscheduled, no-op -- with an addressable ID + p.cue( "a" ); + + equal( p.data.trackEvents.byStart.length, 4, "Declare a cue: unscheduled, no-op -- with an addressable ID, p.cue( 'a' );" ); + + + // Declare a cue: scheduled, no-op -- with an addressable ID + p.cue( "b", 11 ); + + equal( p.data.trackEvents.byStart.length, 5, "Declare a cue: scheduled, no-op -- with an addressable ID, p.cue( 'b', time );" ); + + + // Declare a cue: unscheduled -- with an addressable ID + p.cue( "c", function() {}); + + equal( p.data.trackEvents.byStart.length, 6, "Declare a cue: unscheduled -- with an addressable ID, p.cue( 'c', function );" ); + + + // Declare a cue: scheduled -- with an addressable ID + p.cue( "d", 12, function() {}); + + equal( p.data.trackEvents.byStart.length, 7, "Declare a cue: scheduled -- with an addressable ID, p.cue( 'd', 12, function );" ); + + + // Modify an existing cue's time + p.cue( "c", 13 ); + + equal( p.data.trackEvents.byStart.length, 7, "Modify an existing cue's time, p.cue( 'c', time );" ); + + equal( p.getTrackEvent( "c").start, 13, "Time modified, 13" ); + + + // Modify an existing cue's function + p.cue( "c", function named() {}); + + + equal( p.data.trackEvents.byStart.length, 7, "Modify an existing cue's function, p.cue( 'c', function() {} );" ); + + equal( p.getTrackEvent( "c")._natives.start.name, "named", "Function modified, named" ); + + + // Modify an existing cue's time and function + p.cue( "c", 14, function renamed() {} ); + + + equal( p.data.trackEvents.byStart.length, 7, "Modify an existing cue's time and function, p.cue( 'c', 14, function renamed() {});" ); + + equal( p.getTrackEvent( "c").start, 14, "Time modified, 14" ); + + + equal( p.getTrackEvent( "c")._natives.start.name, "renamed", "Function modified, renamed" ); + + + start(); + p.destroy(); + }); + +}); + asyncTest( "Modify cue or track event after creation", 6, function() { var p = Popcorn( "#video" ), passed = 0; @@ -4365,6 +4437,7 @@ asyncTest( "Modify cue or track event after creation", 6, function() { if ( passed === 4 ) { start(); + p.destroy(); } } }); @@ -4418,6 +4491,36 @@ asyncTest( "Modify cue or track event after creation", 6, function() { }); +asyncTest( "Create empty cue and modify later", 4, function() { + var p = Popcorn( "#video" ); + + p.on( "cuechange", function( data ) { + ok( true, "'cuechange' event fired" ); + }); + + p.on( "canplayall", function() { + + // create an empty cue that does nothing + p.cue( "empty-cue" ); + + equal( p.data.trackEvents.byStart[1].id, "empty-cue", "'empty-cue' was created" ); + + // update the empty cue to do something at 10s + p.cue( "empty-cue", 10, function() { + + ok( true, "'empty-cue' at 10s seconds should fire, even though it started as empty cue" ); + + start(); + p.destroy(); + }); + + equal( p.data.trackEvents.byStart[1].start, 10, "'empty-cue' was updated" ); + + p.play( 9 ); + }); + +}); + module( "Popcorn XHR" ); test( "Basic", function() {