Skip to content

Commit

Permalink
Merge pull request videojs#18 from dmlap/feature/multi-display
Browse files Browse the repository at this point in the history
Add support for ad integrations that use a separate video tech for ad playback
  • Loading branch information
dmlap committed Dec 11, 2013
2 parents 704edfb + 87e78bf commit f8fa6b2
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 5 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ Here's a state diagram which shows the states of the ads plugin and how it trans

![](ad-states.png)

The ads plugin starts in the `init` state and immediately transitions to `content-set` if a video is loaded.
Transitions with solid arrows are traversed when an event with the appropriate type is triggered on the player.
Dotted-line arrows indicate a transition that occurs when a timeout expires.
The timeline at right shows how the ads plugin communicates with your integration.

## Plugin Options
Expand All @@ -108,7 +111,7 @@ player.ads({
});
```

Two options are currently available: `timeout` and `prerollTimeout`.
The current set of options are described in detail below.

### timeout

Expand Down Expand Up @@ -152,6 +155,14 @@ The prerollTimout should be as short as possible so that the viewer does not hav
Make this longer if your ad integration needs a long time to decide whether it has preroll inventory to play or not.
Ideally, your ad integration should already know if it wants to play a preroll before the `readyforpreroll` event.

### debug

Type: `boolean`
Default Value: false

If debug is set to true, the ads plugin will output additional information about its current state during playback.
This can be handy for diagnosing issues or unexpected behavior in an ad integration.

## Building

You can use the `videojs.ads.js` file as it is in the `src/` directory, or you can use a minified version.
Expand Down
26 changes: 22 additions & 4 deletions src/videojs.ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ var
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.setTimeout
).call(window, callback, 0);
)(callback, 0);
},

/**
Expand Down Expand Up @@ -124,7 +124,7 @@ var
// deregister the cancel timeout so subsequent cancels are scheduled
player.ads.cancelPlayTimeout = null;

if (!player.paused()) { // TODO
if (!player.paused()) {
player.pause();
}
});
Expand Down Expand Up @@ -170,7 +170,8 @@ var
},

/**
* Attempts to modify the specified player so that its state is equivalent to the state of the snapshot.
* Attempts to modify the specified player so that its state is equivalent to
* the state of the snapshot.
* @param {object} snapshot - the player state to apply
*/
restorePlayerSnapshot = function(player, snapshot) {
Expand Down Expand Up @@ -213,6 +214,14 @@ var
if (snapshot.nativePoster) {
tech.poster = snapshot.nativePoster;
}

// with a custom ad display or burned-in ads, the content player state
// hasn't been modified and so no restoration is required
if (player.currentSrc() === snapshot.src) {
player.play();
return;
}

player.src(snapshot.src);
// safari requires a call to `load` to pick up a changed source
player.load();
Expand Down Expand Up @@ -249,7 +258,12 @@ var
// maximum amount of time in ms to wait for the ad implementation to start
// linear ad mode after `readyforpreroll` has fired. This is in addition to
// the standard timeout.
prerollTimeout: 100
prerollTimeout: 100,

// when truthy, instructs the plugin to output additional information about
// plugin state to the video.js log. On most devices, the video.js log is
// the same as the developer console.
debug: false
},

adFramework = function(options) {
Expand Down Expand Up @@ -430,6 +444,10 @@ var
if (state !== player.ads.state) {
(fsm[state].leave || noop).apply(player.ads);
(fsm[player.ads.state].enter || noop).apply(player.ads);

if (settings.debug) {
videojs.log('ads', state + ' -> ' + player.ads.state);
}
}

})(player.ads.state);
Expand Down
54 changes: 54 additions & 0 deletions test/videojs.ads.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ module('Ad Framework - Video Snapshot', {
}
});

test('restores the original video src after ads', function() {
var originalSrc = player.currentSrc();

player.trigger('adsready');
player.trigger('play');

player.trigger('adstart');
player.src('//example.com/ad.mp4');
player.trigger('adend');

equal(originalSrc, player.currentSrc(), 'the original src is restored');
});

test('waits for the video to become seekable before restoring the time', function() {
expect(2);

Expand All @@ -346,6 +359,7 @@ test('waits for the video to become seekable before restoring the time', functio
timeouts = 0;
video.currentTime = 100;
player.trigger('adstart');
player.src('//example.com/ad.mp4');

// the ad resets the current time
video.currentTime = 0;
Expand Down Expand Up @@ -376,6 +390,7 @@ test('tries to restore the play state up to 20 times', function() {
timeouts = 0;
video.currentTime = 100;
player.trigger('adstart');
player.src('//example.com/ad.mp4');

// the ad resets the current time
video.currentTime = 0;
Expand All @@ -394,6 +409,7 @@ test('the current time is restored at the end of an ad', function() {

// the video plays to time 100
player.trigger('adstart');
player.src('//exampe.com/ad.mp4');

// the ad resets the current time
video.currentTime = 0;
Expand All @@ -402,3 +418,41 @@ test('the current time is restored at the end of an ad', function() {

equal(100, video.currentTime, 'currentTime was restored');
});

test('only restores the player snapshot if the src changed', function() {
var
playCalled = false,
srcModified = false,
currentTimeModified = false;

player.trigger('adsready');
player.trigger('play');

// spy on relevant player methods
player.play = function() {
playCalled = true;
};
player.src = function(url) {
if (url === undefined) {
return video.src;
}
srcModified = true;
};
player.currentTime = function() {
currentTimeModified = true;
};

// with a separate video display or server-side ad insertion, ads play but
// the src never changes. Modifying the src or currentTime would introduce
// unnecessary seeking and rebuffering
player.trigger('adstart');
player.trigger('adend');

ok(!srcModified, 'the src was not set');
ok(playCalled, 'content playback resumed');

// the src wasn't changed, so we shouldn't be waiting on loadedmetadata to
// update the currentTime
player.trigger('loadedmetadata');
ok(!currentTimeModified, 'no seeking occurred');
});

0 comments on commit f8fa6b2

Please sign in to comment.