Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't send playing before prerolls, just after (and major commenting for redispatch) #187

Merged
merged 6 commits into from Sep 29, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
130 changes: 104 additions & 26 deletions src/videojs.ads.js
Expand Up @@ -318,7 +318,8 @@ var
(function() {
var videoEvents = VIDEO_EVENTS.concat([
'firstplay',
'loadedalldata'
'loadedalldata',
'playing'
]);

var returnTrue = function() { return true; };
Expand All @@ -337,43 +338,119 @@ var
};

player.on(videoEvents, function redispatch(event) {
if (player.ads.state === 'ad-playback') {

// We do a quick play/pause before we check for prerolls. This creates a "playing"
// event. This conditional block prefixes that event so it's "adplaying" if it
// happens while we're in the "preroll?" state. Not every browser is in the
// "preroll?" state for this event, so the following browsers come through here:
// * iPad
// * iPhone
// * Android
// * Safari
// This is too soon to check videoElementRecycled because there is no snapshot
// yet. We rely on the coincidence that all browsers for which
// videoElementRecycled would be true also happen to send their initial playing
// event during "preroll?"
if (event.type === 'playing' && player.ads.state === 'preroll?') {
triggerEvent('ad', event);

// Here we send "adplaying" for browsers that send their initial "playing" event
// (caused by the the initial play/pause) during the "ad-playback" state.
// The following browsers come through here:
// * Chrome
// * IE11
// If the ad plays in the content tech (aka videoElementRecycled) there will be
// another playing event when the ad starts. We check videoElementRecycled to
// avoid a second adplaying event. Thankfully, at this point a snapshot exists
// so we can safely check videoElementRecycled.
} else if (event.type === 'playing' &&
player.ads.state === 'ad-playback' &&
!player.ads.videoElementRecycled()) {
triggerEvent('ad', event);

// When an ad is playing in content tech, we would normally prefix
// "playing" with "ad" to send "adplaying". However, when we did a play/pause
// before the preroll, we already sent "adplaying". This condition prevents us
// from sending another.
} else if (event.type === 'playing' &&
player.ads.state === 'ad-playback' &&
player.ads.videoElementRecycled()) {

// Triggering an event prevents the unprefixed one from firing.
// "adcontentplaying" is only seen in this very specific condition.
triggerEvent('adcontent', event);
return;

// When ad is playing in content tech, prefix everything with "ad".
// This block catches many events such as emptied, play, timeupdate, and ended.
} else if (player.ads.state === 'ad-playback') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this change and think this makes the event flow much easier to follow. However, this is likely to affect other plugins relying on these events and so should be thoroughly documented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I already know of one place where the change to "playing" would break something else. This will be tricky

if (player.ads.videoElementRecycled() || player.ads.stitchedAds()) {
triggerEvent('ad', event);
}

// Send contentended if ended happens during content.
// We will make sure an ended event is sent after postrolls.
} else if (player.ads.state === 'content-playback' && event.type === 'ended') {
triggerEvent('content', event);

// Event prefixing during content resuming is complicated
} else if (player.ads.state === 'content-resuming') {
if (player.ads.snapshot) {
// the video element was recycled for ad playback
if (player.currentSrc() !== player.ads.snapshot.currentSrc) {
if (event.type === 'loadstart') {
return;
}
return triggerEvent('content', event);

// we ended playing postrolls and the video itself
// the content src is back in place
} else if (player.ads.snapshot.ended) {
if ((event.type === 'pause' ||
event.type === 'ended')) {
// after loading a video, the natural state is to not be started
// in this case, it actually has, so, we do it manually
player.addClass('vjs-has-started');
// let `pause` and `ended` events through, naturally
return;
}
// prefix all other events in content-resuming with `content`
return triggerEvent('content', event);

// This does not happen during normal circumstances. I wasn't able to reproduce
// it, but the working theory is that it handles cases where restoring the
// snapshot takes a long time, such as in iOS7 and older Firefox.
if (player.ads.snapshot &&
player.currentSrc() !== player.ads.snapshot.currentSrc) {

// Don't prefix `loadstart` event
if (event.type === 'loadstart') {
return;
}

// All other events get "content" prefix
return triggerEvent('content', event);
}

// Content resuming after postroll
else if (player.ads.snapshot && player.ads.snapshot.ended) {

// Don't prefix `pause` and `ended` events
// They don't always happen during content-resuming, but they might.
// It seems to happen most often on iOS and Android.
if ((event.type === 'pause' ||
event.type === 'ended')) {
return;
}

// All other events get "content" prefix
return triggerEvent('content', event);
}
if (event.type !== 'playing') {
triggerEvent('content', event);

// Content resuming after preroll or midroll
else {

// Events besides "playing" get "content" prefix
if (event.type !== 'playing') {
triggerEvent('content', event);
}

}
}
});

})();

// "vjs-has-started" should be present at the end of a video. In this case we need
// to re-add it manually.
// Not sure why this happens on ended, I've never seen a case where that is needed.
player.on(['pause', 'ended'], function redispatch() {
if (player.ads.state === 'content-resuming' &&
player.ads.snapshot &&
player.ads.snapshot.ended) {
player.addClass('vjs-has-started');
}
});

// We now auto-play when an ad gets loaded if we're playing ads in the same video element as the content.
// The problem is that in IE11, we cannot play in addurationchange but in iOS8, we cannot play from adcanplay.
// This will allow ad-integrations from needing to do this themselves.
Expand Down Expand Up @@ -445,7 +522,8 @@ var
var currentSrcChanged;

if (!this.snapshot) {
return false;
throw new Error(
'You cannot use videoElementRecycled while there is no snapshot.');
}

srcChanged = player.src() !== this.snapshot.src;
Expand Down
6 changes: 3 additions & 3 deletions test/videojs.ads.test.js
Expand Up @@ -880,7 +880,7 @@ QUnit.test('player events during prerolls are prefixed if tech is reused for ad'

// simulate video events that should be prefixed
this.player.on(['loadstart', 'playing', 'pause', 'ended', 'firstplay', 'loadedalldata'], unprefixed);
this.player.on(['adloadstart', 'adplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.on(['adloadstart', 'adcontentplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.trigger('firstplay');
this.player.trigger('loadstart');
this.player.trigger('playing');
Expand Down Expand Up @@ -911,7 +911,7 @@ QUnit.test('player events during midrolls are prefixed if tech is reused for ad'

// simulate video events that should be prefixed
this.player.on(['loadstart', 'playing', 'pause', 'ended', 'firstplay', 'loadedalldata'], unprefixed);
this.player.on(['adloadstart', 'adplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.on(['adloadstart', 'adcontentplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.trigger('firstplay');
this.player.trigger('loadstart');
this.player.trigger('playing');
Expand Down Expand Up @@ -943,7 +943,7 @@ QUnit.test('player events during postrolls are prefixed if tech is reused for ad

// simulate video events that should be prefixed
this.player.on(['loadstart', 'playing', 'pause', 'ended', 'firstplay', 'loadedalldata'], unprefixed);
this.player.on(['adloadstart', 'adplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.on(['adloadstart', 'adcontentplaying', 'adpause', 'adended', 'adfirstplay', 'adloadedalldata'], prefixed);
this.player.trigger('firstplay');
this.player.trigger('loadstart');
this.player.trigger('playing');
Expand Down