740 lines
No EOL
25 KiB
JavaScript
Executable file
740 lines
No EOL
25 KiB
JavaScript
Executable file
/**
|
|
* Basic Ad support plugin for video.js.
|
|
*
|
|
* Common code to support ad integrations.
|
|
*/
|
|
(function(window, videojs, undefined) {
|
|
'use strict';
|
|
|
|
var
|
|
|
|
VIDEO_EVENTS = videojs.getComponent('Html5').Events,
|
|
|
|
/**
|
|
* If ads are not playing, pauses the player at the next available
|
|
* opportunity. Has no effect if ads have started. This function is necessary
|
|
* because pausing a video element while processing a `play` event on iOS can
|
|
* cause the video element to continuously toggle between playing and paused
|
|
* states.
|
|
*
|
|
* @param {object} player The video player
|
|
*/
|
|
cancelContentPlay = function(player) {
|
|
if (player.ads.cancelPlayTimeout) {
|
|
// another cancellation is already in flight, so do nothing
|
|
return;
|
|
}
|
|
player.ads.cancelPlayTimeout = window.setTimeout(function() {
|
|
// deregister the cancel timeout so subsequent cancels are scheduled
|
|
player.ads.cancelPlayTimeout = null;
|
|
|
|
// pause playback so ads can be handled.
|
|
if (!player.paused()) {
|
|
player.pause();
|
|
}
|
|
|
|
// add a contentplayback handler to resume playback when ads finish.
|
|
player.one('contentplayback', function() {
|
|
if (player.paused()) {
|
|
player.play();
|
|
}
|
|
});
|
|
}, 1);
|
|
},
|
|
|
|
/**
|
|
* Returns an object that captures the portions of player state relevant to
|
|
* video playback. The result of this function can be passed to
|
|
* restorePlayerSnapshot with a player to return the player to the state it
|
|
* was in when this function was invoked.
|
|
* @param {object} player The videojs player object
|
|
*/
|
|
getPlayerSnapshot = function(player) {
|
|
var
|
|
tech = player.$('.vjs-tech'),
|
|
tracks = player.remoteTextTracks ? player.remoteTextTracks() : [],
|
|
track,
|
|
i,
|
|
suppressedTracks = [],
|
|
snapshot = {
|
|
ended: player.ended(),
|
|
currentSrc: player.currentSrc(),
|
|
src: player.src(),
|
|
currentTime: player.currentTime(),
|
|
type: player.currentType()
|
|
};
|
|
|
|
if (tech) {
|
|
snapshot.nativePoster = tech.poster;
|
|
snapshot.style = tech.getAttribute('style');
|
|
}
|
|
|
|
i = tracks.length;
|
|
while (i--) {
|
|
track = tracks[i];
|
|
suppressedTracks.push({
|
|
track: track,
|
|
mode: track.mode
|
|
});
|
|
track.mode = 'disabled';
|
|
}
|
|
snapshot.suppressedTracks = suppressedTracks;
|
|
|
|
return 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) {
|
|
var
|
|
// the playback tech
|
|
tech = player.$('.vjs-tech'),
|
|
|
|
// the number of remaining attempts to restore the snapshot
|
|
attempts = 20,
|
|
|
|
suppressedTracks = snapshot.suppressedTracks,
|
|
trackSnapshot,
|
|
restoreTracks = function() {
|
|
var i = suppressedTracks.length;
|
|
while (i--) {
|
|
trackSnapshot = suppressedTracks[i];
|
|
trackSnapshot.track.mode = trackSnapshot.mode;
|
|
}
|
|
},
|
|
|
|
// finish restoring the playback state
|
|
resume = function() {
|
|
var
|
|
ended = false,
|
|
updateEnded = function() {
|
|
ended = true;
|
|
};
|
|
player.currentTime(snapshot.currentTime);
|
|
|
|
// Resume playback if this wasn't a postroll
|
|
if (!snapshot.ended) {
|
|
player.play();
|
|
} else {
|
|
// On iOS 8.1, the "ended" event will not fire if you seek
|
|
// directly to the end of a video. To make that behavior
|
|
// consistent with the standard, fire a synthetic event if
|
|
// "ended" does not fire within 250ms. Note that the ended
|
|
// event should occur whether the browser actually has data
|
|
// available for that position
|
|
// (https://html.spec.whatwg.org/multipage/embedded-content.html#seeking),
|
|
// so it should not be necessary to wait for the seek to
|
|
// indicate completion.
|
|
player.ads.resumeEndedTimeout = window.setTimeout(function() {
|
|
if (!ended) {
|
|
player.play();
|
|
}
|
|
player.off('ended', updateEnded);
|
|
player.ads.resumeEndedTimeout = null;
|
|
}, 250);
|
|
player.on('ended', updateEnded);
|
|
|
|
// Need to clear the resume/ended timeout on dispose. If it fires
|
|
// after a player is disposed, an error will be thrown!
|
|
player.on('dispose', function() {
|
|
window.clearTimeout(player.ads.resumeEndedTimeout);
|
|
});
|
|
}
|
|
},
|
|
|
|
// determine if the video element has loaded enough of the snapshot source
|
|
// to be ready to apply the rest of the state
|
|
tryToResume = function() {
|
|
|
|
// tryToResume can either have been called through the `contentcanplay`
|
|
// event or fired through setTimeout.
|
|
// When tryToResume is called, we should make sure to clear out the other
|
|
// way it could've been called by removing the listener and clearing out
|
|
// the timeout.
|
|
player.off('contentcanplay', tryToResume);
|
|
if (player.ads.tryToResumeTimeout_) {
|
|
player.clearTimeout(player.ads.tryToResumeTimeout_);
|
|
player.ads.tryToResumeTimeout_ = null;
|
|
}
|
|
|
|
// Tech may have changed depending on the differences in sources of the
|
|
// original video and that of the ad
|
|
tech = player.el().querySelector('.vjs-tech');
|
|
|
|
if (tech.readyState > 1) {
|
|
// some browsers and media aren't "seekable".
|
|
// readyState greater than 1 allows for seeking without exceptions
|
|
return resume();
|
|
}
|
|
|
|
if (tech.seekable === undefined) {
|
|
// if the tech doesn't expose the seekable time ranges, try to
|
|
// resume playback immediately
|
|
return resume();
|
|
}
|
|
|
|
if (tech.seekable.length > 0) {
|
|
// if some period of the video is seekable, resume playback
|
|
return resume();
|
|
}
|
|
|
|
// delay a bit and then check again unless we're out of attempts
|
|
if (attempts--) {
|
|
window.setTimeout(tryToResume, 50);
|
|
} else {
|
|
(function() {
|
|
try {
|
|
resume();
|
|
} catch(e) {
|
|
videojs.log.warn('Failed to resume the content after an advertisement', e);
|
|
}
|
|
})();
|
|
}
|
|
},
|
|
|
|
// whether the video element has been modified since the
|
|
// snapshot was taken
|
|
srcChanged;
|
|
|
|
if (snapshot.nativePoster) {
|
|
tech.poster = snapshot.nativePoster;
|
|
}
|
|
|
|
if ('style' in snapshot) {
|
|
// overwrite all css style properties to restore state precisely
|
|
tech.setAttribute('style', snapshot.style || '');
|
|
}
|
|
|
|
// Determine whether the player needs to be restored to its state
|
|
// before ad playback began. With a custom ad display or burned-in
|
|
// ads, the content player state hasn't been modified and so no
|
|
// restoration is required
|
|
|
|
srcChanged = player.src() !== snapshot.src || player.currentSrc() !== snapshot.currentSrc;
|
|
|
|
if (srcChanged) {
|
|
// on ios7, fiddling with textTracks too early will cause safari to crash
|
|
player.one('contentloadedmetadata', restoreTracks);
|
|
|
|
// if the src changed for ad playback, reset it
|
|
player.src({ src: snapshot.currentSrc, type: snapshot.type });
|
|
// safari requires a call to `load` to pick up a changed source
|
|
player.load();
|
|
// and then resume from the snapshots time once the original src has loaded
|
|
// in some browsers (firefox) `canplay` may not fire correctly.
|
|
// Reace the `canplay` event with a timeout.
|
|
player.one('contentcanplay', tryToResume);
|
|
player.ads.tryToResumeTimeout_ = player.setTimeout(tryToResume, 2000);
|
|
} else if (!player.ended() || !snapshot.ended) {
|
|
// if we didn't change the src, just restore the tracks
|
|
restoreTracks();
|
|
// the src didn't change and this wasn't a postroll
|
|
// just resume playback at the current time.
|
|
player.play();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove the poster attribute from the video element tech, if present. When
|
|
* reusing a video element for multiple videos, the poster image will briefly
|
|
* reappear while the new source loads. Removing the attribute ahead of time
|
|
* prevents the poster from showing up between videos.
|
|
* @param {object} player The videojs player object
|
|
*/
|
|
removeNativePoster = function(player) {
|
|
var tech = player.$('.vjs-tech');
|
|
if (tech) {
|
|
tech.removeAttribute('poster');
|
|
}
|
|
},
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Ad Framework
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// default framework settings
|
|
defaults = {
|
|
// maximum amount of time in ms to wait to receive `adsready` from the ad
|
|
// implementation after play has been requested. Ad implementations are
|
|
// expected to load any dynamic libraries and make any requests to determine
|
|
// ad policies for a video during this time.
|
|
timeout: 5000,
|
|
|
|
// 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,
|
|
|
|
// maximum amount of time in ms to wait for the ad implementation to start
|
|
// linear ad mode after `contentended` has fired.
|
|
postrollTimeout: 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) {
|
|
var player = this;
|
|
var settings = videojs.mergeOptions(defaults, options);
|
|
var fsmHandler;
|
|
|
|
// prefix all video element events during ad playback
|
|
// if the video element emits ad-related events directly,
|
|
// plugins that aren't ad-aware will break. prefixing allows
|
|
// plugins that wish to handle ad events to do so while
|
|
// avoiding the complexity for common usage
|
|
(function() {
|
|
var videoEvents = VIDEO_EVENTS.concat([
|
|
'firstplay',
|
|
'loadedalldata'
|
|
]);
|
|
|
|
var returnTrue = function() { return true; };
|
|
|
|
var triggerEvent = function(type, event) {
|
|
// pretend we called stopImmediatePropagation because we want the native
|
|
// element events to continue propagating
|
|
event.isImmediatePropagationStopped = returnTrue;
|
|
event.cancelBubble = true;
|
|
event.isPropagationStopped = returnTrue;
|
|
player.trigger({
|
|
type: type + event.type,
|
|
state: player.ads.state,
|
|
originalEvent: event
|
|
});
|
|
};
|
|
|
|
player.on(videoEvents, function redispatch(event) {
|
|
if (player.ads.state === 'ad-playback') {
|
|
triggerEvent('ad', event);
|
|
} else if (player.ads.state === 'content-playback' && event.type === 'ended') {
|
|
triggerEvent('content', event);
|
|
} 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);
|
|
}
|
|
}
|
|
if (event.type !== 'playing') {
|
|
triggerEvent('content', event);
|
|
}
|
|
}
|
|
});
|
|
})();
|
|
|
|
// 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.
|
|
player.on(['addurationchange', 'adcanplay'], function() {
|
|
if (player.currentSrc() === player.ads.snapshot.currentSrc) {
|
|
return;
|
|
}
|
|
|
|
player.play();
|
|
});
|
|
|
|
player.on('nopreroll', function() {
|
|
player.ads.nopreroll_ = true;
|
|
});
|
|
|
|
player.on('nopostroll', function() {
|
|
player.ads.nopostroll_ = true;
|
|
});
|
|
|
|
// replace the ad initializer with the ad namespace
|
|
player.ads = {
|
|
state: 'content-set',
|
|
|
|
// Call this when an ad response has been received and there are
|
|
// linear ads ready to be played.
|
|
startLinearAdMode: function() {
|
|
if (player.ads.state === 'preroll?' ||
|
|
player.ads.state === 'content-playback' ||
|
|
player.ads.state === 'postroll?') {
|
|
player.trigger('adstart');
|
|
}
|
|
},
|
|
|
|
// Call this when a linear ad pod has finished playing.
|
|
endLinearAdMode: function() {
|
|
if (player.ads.state === 'ad-playback') {
|
|
player.trigger('adend');
|
|
}
|
|
},
|
|
|
|
// Call this when an ad response has been received but there are no
|
|
// linear ads to be played (i.e. no ads available, or overlays).
|
|
// This has no effect if we are already in a linear ad mode. Always
|
|
// use endLinearAdMode() to exit from linear ad-playback state.
|
|
skipLinearAdMode: function() {
|
|
if (player.ads.state !== 'ad-playback') {
|
|
player.trigger('adskip');
|
|
}
|
|
}
|
|
};
|
|
|
|
fsmHandler = function(event) {
|
|
// Ad Playback State Machine
|
|
var fsm = {
|
|
'content-set': {
|
|
events: {
|
|
'adscanceled': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adsready': function() {
|
|
this.state = 'ads-ready';
|
|
},
|
|
'play': function() {
|
|
this.state = 'ads-ready?';
|
|
cancelContentPlay(player);
|
|
// remove the poster so it doesn't flash between videos
|
|
removeNativePoster(player);
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adskip': function() {
|
|
this.state = 'content-playback';
|
|
}
|
|
}
|
|
},
|
|
'ads-ready': {
|
|
events: {
|
|
'play': function() {
|
|
this.state = 'preroll?';
|
|
cancelContentPlay(player);
|
|
},
|
|
'adskip': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-playback';
|
|
}
|
|
}
|
|
},
|
|
'preroll?': {
|
|
enter: function() {
|
|
if (player.ads.nopreroll_) {
|
|
// This will start the ads manager in case there are later ads
|
|
player.trigger('readyforpreroll');
|
|
// Don't wait for a preroll
|
|
player.trigger('nopreroll');
|
|
} else {
|
|
// change class to show that we're waiting on ads
|
|
player.addClass('vjs-ad-loading');
|
|
// schedule an adtimeout event to fire if we waited too long
|
|
player.ads.adTimeoutTimeout = window.setTimeout(function() {
|
|
player.trigger('adtimeout');
|
|
}, settings.prerollTimeout);
|
|
// signal to ad plugin that it's their opportunity to play a preroll
|
|
player.trigger('readyforpreroll');
|
|
}
|
|
},
|
|
leave: function() {
|
|
window.clearTimeout(player.ads.adTimeoutTimeout);
|
|
player.removeClass('vjs-ad-loading');
|
|
},
|
|
events: {
|
|
'play': function() {
|
|
cancelContentPlay(player);
|
|
},
|
|
'adstart': function() {
|
|
this.state = 'ad-playback';
|
|
},
|
|
'adskip': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adtimeout': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'nopreroll': function() {
|
|
this.state = 'content-playback';
|
|
}
|
|
}
|
|
},
|
|
'ads-ready?': {
|
|
enter: function() {
|
|
player.addClass('vjs-ad-loading');
|
|
player.ads.adTimeoutTimeout = window.setTimeout(function() {
|
|
player.trigger('adtimeout');
|
|
}, settings.timeout);
|
|
},
|
|
leave: function() {
|
|
window.clearTimeout(player.ads.adTimeoutTimeout);
|
|
player.removeClass('vjs-ad-loading');
|
|
},
|
|
events: {
|
|
'play': function() {
|
|
cancelContentPlay(player);
|
|
},
|
|
'adscanceled': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adsready': function() {
|
|
this.state = 'preroll?';
|
|
},
|
|
'adskip': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adtimeout': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-playback';
|
|
}
|
|
}
|
|
},
|
|
'ad-playback': {
|
|
enter: function() {
|
|
// capture current player state snapshot (playing, currentTime, src)
|
|
this.snapshot = getPlayerSnapshot(player);
|
|
|
|
// add css to the element to indicate and ad is playing.
|
|
player.addClass('vjs-ad-playing');
|
|
|
|
// remove the poster so it doesn't flash between ads
|
|
removeNativePoster(player);
|
|
|
|
// We no longer need to supress play events once an ad is playing.
|
|
// Clear it if we were.
|
|
if (player.ads.cancelPlayTimeout) {
|
|
window.clearTimeout(player.ads.cancelPlayTimeout);
|
|
player.ads.cancelPlayTimeout = null;
|
|
}
|
|
},
|
|
leave: function() {
|
|
player.removeClass('vjs-ad-playing');
|
|
restorePlayerSnapshot(player, this.snapshot);
|
|
// trigger 'adend' as a consistent notification
|
|
// event that we're exiting ad-playback.
|
|
if (player.ads.triggerevent !== 'adend') {
|
|
player.trigger('adend');
|
|
}
|
|
},
|
|
events: {
|
|
'adend': function() {
|
|
this.state = 'content-resuming';
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-resuming';
|
|
}
|
|
}
|
|
},
|
|
'content-resuming': {
|
|
enter: function() {
|
|
if (this.snapshot.ended) {
|
|
window.clearTimeout(player.ads._fireEndedTimeout);
|
|
// in some cases, ads are played in a swf or another video element
|
|
// so we do not get an ended event in this state automatically.
|
|
// If we don't get an ended event we can use, we need to trigger
|
|
// one ourselves or else we won't actually ever end the current video.
|
|
player.ads._fireEndedTimeout = window.setTimeout(function() {
|
|
player.trigger('ended');
|
|
}, 1000);
|
|
}
|
|
},
|
|
leave: function() {
|
|
window.clearTimeout(player.ads._fireEndedTimeout);
|
|
},
|
|
events: {
|
|
'contentupdate': function() {
|
|
this.state = 'content-set';
|
|
},
|
|
contentresumed: function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'playing': function() {
|
|
this.state = 'content-playback';
|
|
},
|
|
'ended': function() {
|
|
this.state = 'content-playback';
|
|
}
|
|
}
|
|
},
|
|
'postroll?': {
|
|
enter: function() {
|
|
this.snapshot = getPlayerSnapshot(player);
|
|
|
|
player.addClass('vjs-ad-loading');
|
|
|
|
player.ads.adTimeoutTimeout = window.setTimeout(function() {
|
|
player.trigger('adtimeout');
|
|
}, settings.postrollTimeout);
|
|
},
|
|
leave: function() {
|
|
window.clearTimeout(player.ads.adTimeoutTimeout);
|
|
player.removeClass('vjs-ad-loading');
|
|
},
|
|
events: {
|
|
'adstart': function() {
|
|
this.state = 'ad-playback';
|
|
},
|
|
'adskip': function() {
|
|
this.state = 'content-resuming';
|
|
window.setTimeout(function() {
|
|
player.trigger('ended');
|
|
}, 1);
|
|
},
|
|
'adtimeout': function() {
|
|
this.state = 'content-resuming';
|
|
window.setTimeout(function() {
|
|
player.trigger('ended');
|
|
}, 1);
|
|
},
|
|
'adserror': function() {
|
|
this.state = 'content-resuming';
|
|
window.setTimeout(function() {
|
|
player.trigger('ended');
|
|
}, 1);
|
|
}
|
|
}
|
|
},
|
|
'content-playback': {
|
|
enter: function() {
|
|
// make sure that any cancelPlayTimeout is cleared
|
|
if (player.ads.cancelPlayTimeout) {
|
|
window.clearTimeout(player.ads.cancelPlayTimeout);
|
|
player.ads.cancelPlayTimeout = null;
|
|
}
|
|
// this will cause content to start if a user initiated
|
|
// 'play' event was canceled earlier.
|
|
player.trigger({
|
|
type: 'contentplayback',
|
|
triggerevent: player.ads.triggerevent
|
|
});
|
|
},
|
|
events: {
|
|
// in the case of a timeout, adsready might come in late.
|
|
'adsready': function() {
|
|
player.trigger('readyforpreroll');
|
|
},
|
|
'adstart': function() {
|
|
this.state = 'ad-playback';
|
|
},
|
|
'contentupdate': function() {
|
|
if (player.paused()) {
|
|
this.state = 'content-set';
|
|
} else {
|
|
this.state = 'ads-ready?';
|
|
}
|
|
},
|
|
'contentended': function() {
|
|
this.state = 'postroll?';
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
(function(state) {
|
|
var noop = function() {};
|
|
|
|
// process the current event with a noop default handler
|
|
((fsm[state].events || {})[event.type] || noop).apply(player.ads);
|
|
|
|
// check whether the state has changed
|
|
if (state !== player.ads.state) {
|
|
|
|
// record the event that caused the state transition
|
|
player.ads.triggerevent = event.type;
|
|
|
|
// execute leave/enter callbacks if present
|
|
(fsm[state].leave || noop).apply(player.ads);
|
|
(fsm[player.ads.state].enter || noop).apply(player.ads);
|
|
|
|
// output debug logging
|
|
if (settings.debug) {
|
|
videojs.log('ads', player.ads.triggerevent + ' triggered: ' + state + ' -> ' + player.ads.state);
|
|
}
|
|
}
|
|
|
|
})(player.ads.state);
|
|
|
|
};
|
|
|
|
// register for the events we're interested in
|
|
player.on(VIDEO_EVENTS.concat([
|
|
// events emitted by ad plugin
|
|
'adtimeout',
|
|
'contentupdate',
|
|
'contentplaying',
|
|
'contentended',
|
|
'contentresumed',
|
|
|
|
// events emitted by third party ad implementors
|
|
'adsready',
|
|
'adserror',
|
|
'adscanceled',
|
|
'adstart', // startLinearAdMode()
|
|
'adend', // endLinearAdMode()
|
|
'adskip', // skipLinearAdMode()
|
|
'nopreroll'
|
|
]), fsmHandler);
|
|
|
|
// keep track of the current content source
|
|
// if you want to change the src of the video without triggering
|
|
// the ad workflow to restart, you can update this variable before
|
|
// modifying the player's source
|
|
player.ads.contentSrc = player.currentSrc();
|
|
|
|
// implement 'contentupdate' event.
|
|
(function(){
|
|
var
|
|
// check if a new src has been set, if so, trigger contentupdate
|
|
checkSrc = function() {
|
|
var src;
|
|
if (player.ads.state !== 'ad-playback') {
|
|
src = player.currentSrc();
|
|
if (src !== player.ads.contentSrc) {
|
|
player.trigger({
|
|
type: 'contentupdate',
|
|
oldValue: player.ads.contentSrc,
|
|
newValue: src
|
|
});
|
|
player.ads.contentSrc = src;
|
|
}
|
|
}
|
|
};
|
|
// loadstart reliably indicates a new src has been set
|
|
player.on('loadstart', checkSrc);
|
|
// check immediately in case we missed the loadstart
|
|
window.setTimeout(checkSrc, 1);
|
|
})();
|
|
|
|
// kick off the fsm
|
|
if (!player.paused()) {
|
|
// simulate a play event if we're autoplaying
|
|
fsmHandler({type:'play'});
|
|
}
|
|
|
|
};
|
|
|
|
// register the ad plugin framework
|
|
videojs.plugin('ads', adFramework);
|
|
|
|
})(window, videojs); |