videojs-contrib-ads
Version:
A framework that provides common functionality needed by video advertisement libraries working with video.js.
1,841 lines (1,424 loc) • 153 kB
JavaScript
(function (QUnit$1,videojs$1) {
'use strict';
QUnit$1 = QUnit$1 && QUnit$1.hasOwnProperty('default') ? QUnit$1['default'] : QUnit$1;
videojs$1 = videojs$1 && videojs$1.hasOwnProperty('default') ? videojs$1['default'] : videojs$1;
var asyncGenerator = function () {
function AwaitValue(value) {
this.value = value;
}
function AsyncGenerator(gen) {
var front, back;
function send(key, arg) {
return new Promise(function (resolve, reject) {
var request = {
key: key,
arg: arg,
resolve: resolve,
reject: reject,
next: null
};
if (back) {
back = back.next = request;
} else {
front = back = request;
resume(key, arg);
}
});
}
function resume(key, arg) {
try {
var result = gen[key](arg);
var value = result.value;
if (value instanceof AwaitValue) {
Promise.resolve(value.value).then(function (arg) {
resume("next", arg);
}, function (arg) {
resume("throw", arg);
});
} else {
settle(result.done ? "return" : "normal", result.value);
}
} catch (err) {
settle("throw", err);
}
}
function settle(type, value) {
switch (type) {
case "return":
front.resolve({
value: value,
done: true
});
break;
case "throw":
front.reject(value);
break;
default:
front.resolve({
value: value,
done: false
});
break;
}
front = front.next;
if (front) {
resume(front.key, front.arg);
} else {
back = null;
}
}
this._invoke = send;
if (typeof gen.return !== "function") {
this.return = undefined;
}
}
if (typeof Symbol === "function" && Symbol.asyncIterator) {
AsyncGenerator.prototype[Symbol.asyncIterator] = function () {
return this;
};
}
AsyncGenerator.prototype.next = function (arg) {
return this._invoke("next", arg);
};
AsyncGenerator.prototype.throw = function (arg) {
return this._invoke("throw", arg);
};
AsyncGenerator.prototype.return = function (arg) {
return this._invoke("return", arg);
};
return {
wrap: function (fn) {
return function () {
return new AsyncGenerator(fn.apply(this, arguments));
};
},
await: function (value) {
return new AwaitValue(value);
}
};
}();
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var State = function () {
function State(player) {
classCallCheck(this, State);
this.player = player;
}
/*
* This is the only allowed way to perform state transitions. State transitions usually
* happen in player event handlers. They can also happen recursively in `init`. They
* should _not_ happen in `cleanup`.
*/
State.prototype.transitionTo = function transitionTo(NewState) {
var player = this.player;
var previousState = this;
previousState.cleanup();
var newState = new NewState(player);
player.ads._state = newState;
player.ads.debug(previousState.constructor.name + ' -> ' + newState.constructor.name);
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
newState.init.apply(newState, [player].concat(args));
};
/*
* Implemented by subclasses to provide initialization logic when transitioning
* to a new state.
*/
State.prototype.init = function init() {};
/*
* Implemented by subclasses to provide cleanup logic when transitioning
* to a new state.
*/
State.prototype.cleanup = function cleanup() {};
/*
* Default event handlers. Different states can override these to provide behaviors.
*/
State.prototype.onPlay = function onPlay() {};
State.prototype.onPlaying = function onPlaying() {};
State.prototype.onEnded = function onEnded() {};
State.prototype.onAdsReady = function onAdsReady() {
videojs$1.log.warn('Unexpected adsready event');
};
State.prototype.onAdsError = function onAdsError() {};
State.prototype.onAdsCanceled = function onAdsCanceled() {};
State.prototype.onAdTimeout = function onAdTimeout() {};
State.prototype.onAdStarted = function onAdStarted() {};
State.prototype.onContentChanged = function onContentChanged() {};
State.prototype.onContentResumed = function onContentResumed() {};
State.prototype.onContentEnded = function onContentEnded() {
videojs$1.log.warn('Unexpected contentended event');
};
State.prototype.onNoPreroll = function onNoPreroll() {};
State.prototype.onNoPostroll = function onNoPostroll() {};
/*
* Method handlers. Different states can override these to provide behaviors.
*/
State.prototype.startLinearAdMode = function startLinearAdMode() {
videojs$1.log.warn('Unexpected startLinearAdMode invocation ' + '(State via ' + this.constructor.name + ')');
};
State.prototype.endLinearAdMode = function endLinearAdMode() {
videojs$1.log.warn('Unexpected endLinearAdMode invocation ' + '(State via ' + this.constructor.name + ')');
};
State.prototype.skipLinearAdMode = function skipLinearAdMode() {
videojs$1.log.warn('Unexpected skipLinearAdMode invocation ' + '(State via ' + this.constructor.name + ')');
};
/*
* Overridden by ContentState and AdState. Should not be overriden elsewhere.
*/
State.prototype.isAdState = function isAdState() {
throw new Error('isAdState unimplemented for ' + this.constructor.name);
};
/*
* Overridden by PrerollState, MidrollState, and PostrollState.
*/
State.prototype.isContentResuming = function isContentResuming() {
return false;
};
State.prototype.inAdBreak = function inAdBreak() {
return false;
};
/*
* Invoke event handler methods when events come in.
*/
State.prototype.handleEvent = function handleEvent(type) {
var player = this.player;
if (type === 'play') {
this.onPlay(player);
} else if (type === 'adsready') {
this.onAdsReady(player);
} else if (type === 'adserror') {
this.onAdsError(player);
} else if (type === 'adscanceled') {
this.onAdsCanceled(player);
} else if (type === 'adtimeout') {
this.onAdTimeout(player);
} else if (type === 'ads-ad-started') {
this.onAdStarted(player);
} else if (type === 'contentchanged') {
this.onContentChanged(player);
} else if (type === 'contentresumed') {
this.onContentResumed(player);
} else if (type === 'contentended') {
this.onContentEnded(player);
} else if (type === 'playing') {
this.onPlaying(player);
} else if (type === 'ended') {
this.onEnded(player);
} else if (type === 'nopreroll') {
this.onNoPreroll(player);
} else if (type === 'nopostroll') {
this.onNoPostroll(player);
}
};
return State;
}();
/*
* This class contains logic for all ads, be they prerolls, midrolls, or postrolls.
* Primarily, this involves handling startLinearAdMode and endLinearAdMode.
* It also handles content resuming.
*/
var AdState = function (_State) {
inherits(AdState, _State);
function AdState(player) {
classCallCheck(this, AdState);
var _this = possibleConstructorReturn(this, _State.call(this, player));
_this.contentResuming = false;
return _this;
}
/*
* Overrides State.isAdState
*/
AdState.prototype.isAdState = function isAdState() {
return true;
};
/*
* We end the content-resuming process on the playing event because this is the exact
* moment that content playback is no longer blocked by ads.
*/
AdState.prototype.onPlaying = function onPlaying() {
if (this.contentResuming) {
this.transitionTo(ContentPlayback);
}
};
/*
* If the integration does not result in a playing event when resuming content after an
* ad, they should instead trigger a contentresumed event to signal that content should
* resume. The main use case for this is when ads are stitched into the content video.
*/
AdState.prototype.onContentResumed = function onContentResumed() {
if (this.contentResuming) {
this.transitionTo(ContentPlayback);
}
};
/*
* Allows you to check if content is currently resuming after an ad break.
*/
AdState.prototype.isContentResuming = function isContentResuming() {
return this.contentResuming;
};
/*
* Allows you to check if an ad break is in progress.
*/
AdState.prototype.inAdBreak = function inAdBreak() {
return this.player.ads._inLinearAdMode === true;
};
return AdState;
}(State);
var ContentState = function (_State) {
inherits(ContentState, _State);
function ContentState() {
classCallCheck(this, ContentState);
return possibleConstructorReturn(this, _State.apply(this, arguments));
}
/*
* Overrides State.isAdState
*/
ContentState.prototype.isAdState = function isAdState() {
return false;
};
/*
* Source change sends you back to preroll checks. contentchanged does not
* fire during ad breaks, so we don't need to worry about that.
*/
ContentState.prototype.onContentChanged = function onContentChanged(player) {
player.ads.debug('Received contentchanged event (ContentState)');
if (player.paused()) {
this.transitionTo(BeforePreroll);
} else {
this.transitionTo(Preroll, false);
player.pause();
player.ads._pausedOnContentupdate = true;
}
};
return ContentState;
}(State);
/*
This feature makes sure the player is paused during ad loading.
It does this by pausing the player immediately after a "play" where ads will be requested,
then signalling that we should play after the ad is done.
*/
function cancelContentPlay(player) {
if (player.ads.cancelPlayTimeout) {
// another cancellation is already in flight, so do nothing
return;
}
// The timeout 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.
player.ads.cancelPlayTimeout = player.setTimeout(function () {
// deregister the cancel timeout so subsequent cancels are scheduled
player.ads.cancelPlayTimeout = null;
if (!player.ads.isInAdMode()) {
return;
}
// pause playback so ads can be handled.
if (!player.paused()) {
player.pause();
}
// When the 'content-playback' state is entered, this will let us know to play.
// This is needed if there is no preroll or if it errors, times out, etc.
player.ads._cancelledPlay = true;
}, 1);
}
var CancelContentPlay = (Object.freeze || Object)({
'default': cancelContentPlay
});
/*
The snapshot feature is responsible for saving the player state before an ad, then
restoring the player state after an ad.
*/
/*
* 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
*/
function getPlayerSnapshot(player) {
var currentTime = void 0;
if (videojs$1.browser.IS_IOS && player.ads.isLive(player)) {
// Record how far behind live we are
if (player.seekable().length > 0) {
currentTime = player.currentTime() - player.seekable().end(0);
} else {
currentTime = player.currentTime();
}
} else {
currentTime = player.currentTime();
}
var tech = player.$('.vjs-tech');
var tracks = player.textTracks ? player.textTracks() : [];
var suppressedTracks = [];
var snapshotObject = {
ended: player.ended(),
currentSrc: player.currentSrc(),
src: player.tech_.src(),
currentTime: currentTime,
type: player.currentType()
};
if (tech) {
snapshotObject.nativePoster = tech.poster;
snapshotObject.style = tech.getAttribute('style');
}
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
suppressedTracks.push({
track: track,
mode: track.mode
});
track.mode = 'disabled';
}
snapshotObject.suppressedTracks = suppressedTracks;
return snapshotObject;
}
/*
* Attempts to modify the specified player so that its state is equivalent to
* the state of the snapshot.
* @param {Object} player - the videojs player object
* @param {Object} snapshotObject - the player state to apply
*/
function restorePlayerSnapshot(player, snapshotObject) {
if (player.ads.disableNextSnapshotRestore === true) {
player.ads.disableNextSnapshotRestore = false;
return;
}
// The playback tech
var tech = player.$('.vjs-tech');
// the number of[ remaining attempts to restore the snapshot
var attempts = 20;
var suppressedTracks = snapshotObject.suppressedTracks;
var trackSnapshot = void 0;
var restoreTracks = function restoreTracks() {
for (var i = 0; i < suppressedTracks.length; i++) {
trackSnapshot = suppressedTracks[i];
trackSnapshot.track.mode = trackSnapshot.mode;
}
};
// finish restoring the playback state
var resume = function resume() {
var currentTime = void 0;
if (videojs$1.browser.IS_IOS && player.ads.isLive(player)) {
if (snapshotObject.currentTime < 0) {
// Playback was behind real time, so seek backwards to match
if (player.seekable().length > 0) {
currentTime = player.seekable().end(0) + snapshotObject.currentTime;
} else {
currentTime = player.currentTime();
}
player.currentTime(currentTime);
}
} else if (snapshotObject.ended) {
player.currentTime(player.duration());
} else {
player.currentTime(snapshotObject.currentTime);
}
// Resume playback if this wasn't a postroll
if (!snapshotObject.ended) {
player.play();
}
// if we added autoplay to force content loading on iOS, remove it now
// that it has served its purpose
if (player.ads.shouldRemoveAutoplay_) {
player.autoplay(false);
player.ads.shouldRemoveAutoplay_ = false;
}
};
// determine if the video element has loaded enough of the snapshot source
// to be ready to apply the rest of the state
var tryToResume = function tryToResume() {
// 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--) {
player.setTimeout(tryToResume, 50);
} else {
try {
resume();
} catch (e) {
videojs$1.log.warn('Failed to resume the content after an advertisement', e);
}
}
};
if (snapshotObject.nativePoster) {
tech.poster = snapshotObject.nativePoster;
}
if ('style' in snapshotObject) {
// overwrite all css style properties to restore state precisely
tech.setAttribute('style', snapshotObject.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
if (player.ads.videoElementRecycled()) {
// on ios7, fiddling with textTracks too early will cause safari to crash
player.one('contentloadedmetadata', restoreTracks);
// adding autoplay guarantees that Safari will load the content so we can
// seek back to the correct time after ads
if (videojs$1.browser.IS_IOS && !player.autoplay()) {
player.autoplay(true);
// if we get here, the player was not originally configured to autoplay,
// so we should remove it after it has served its purpose
player.ads.shouldRemoveAutoplay_ = true;
}
// if the src changed for ad playback, reset it
player.src({ src: snapshotObject.currentSrc, type: snapshotObject.type });
// 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 we didn't change the src, just restore the tracks
restoreTracks();
// we don't need to check snapshotObject.ended here because the content video
// element wasn't recycled
if (!player.ended()) {
// the src didn't change and this wasn't a postroll
// just resume playback at the current time.
player.play();
}
}
}
/*
* Encapsulates logic for starting and ending ad breaks. An ad break
* is the time between startLinearAdMode and endLinearAdMode. The ad
* integration may play 0 or more ads during this time.
*/
function start(player) {
player.ads.debug('Starting ad break');
player.ads._inLinearAdMode = true;
// No longer does anything, used to move us to ad-playback
player.trigger('adstart');
// Capture current player state snapshot
if (!player.ads.shouldPlayContentBehindAd(player)) {
player.ads.snapshot = getPlayerSnapshot(player);
}
// Mute the player behind the ad
if (player.ads.shouldPlayContentBehindAd(player)) {
player.ads.preAdVolume_ = player.volume();
player.volume(0);
}
// Add css to the element to indicate and ad is playing.
player.addClass('vjs-ad-playing');
// We should remove the vjs-live class if it has been added in order to
// show the adprogress control bar on Android devices for falsely
// determined LIVE videos due to the duration incorrectly reported as Infinity
if (player.hasClass('vjs-live')) {
player.removeClass('vjs-live');
}
// This removes the native poster so the ads don't show the content
// poster if content element is reused for ad playback. The snapshot
// will restore it afterwards.
player.ads.removeNativePoster();
}
function end(player) {
player.ads.debug('Ending ad break');
player.ads.adType = null;
player.ads._inLinearAdMode = false;
// Signals the end of the ad break to anyone listening.
player.trigger('adend');
player.removeClass('vjs-ad-playing');
// We should add the vjs-live class back if the video is a LIVE video
// If we dont do this, then for a LIVE Video, we will get an incorrect
// styled control, which displays the time for the video
if (player.ads.isLive(player)) {
player.addClass('vjs-live');
}
if (!player.ads.shouldPlayContentBehindAd(player)) {
restorePlayerSnapshot(player, player.ads.snapshot);
}
// Reset the volume to pre-ad levels
if (player.ads.shouldPlayContentBehindAd(player)) {
player.volume(player.ads.preAdVolume_);
}
}
var obj = { start: start, end: end };
/*
* This state encapsulates waiting for prerolls, preroll playback, and
* content restoration after a preroll.
*/
var Preroll = function (_AdState) {
inherits(Preroll, _AdState);
function Preroll() {
classCallCheck(this, Preroll);
return possibleConstructorReturn(this, _AdState.apply(this, arguments));
}
Preroll.prototype.init = function init(player, adsReady) {
// Loading spinner from now until ad start or end of ad break.
player.addClass('vjs-ad-loading');
// Determine preroll timeout based on plugin settings
var timeout = player.ads.settings.timeout;
if (typeof player.ads.settings.prerollTimeout === 'number') {
timeout = player.ads.settings.prerollTimeout;
}
// Start the clock ticking for ad timeout
this._timeout = player.setTimeout(function () {
player.trigger('adtimeout');
}, timeout);
// If adsready already happened, lets get started. Otherwise,
// wait until onAdsReady.
if (adsReady) {
this.handleAdsReady();
} else {
this.adsReady = false;
}
};
Preroll.prototype.onAdsReady = function onAdsReady(player) {
if (!player.ads.inAdBreak() && !player.ads.isContentResuming()) {
player.ads.debug('Received adsready event (Preroll)');
this.handleAdsReady();
} else {
videojs$1.log.warn('Unexpected adsready event (Preroll)');
}
};
/*
* Ad integration is ready. Let's get started on this preroll.
*/
Preroll.prototype.handleAdsReady = function handleAdsReady() {
this.adsReady = true;
if (this.player.ads.nopreroll_) {
this.noPreroll();
} else {
this.readyForPreroll();
}
};
/*
* Helper to call a callback only after a loadstart event.
* If we start content or ads before loadstart, loadstart
* will not be prefixed correctly.
*/
Preroll.prototype.afterLoadStart = function afterLoadStart(callback) {
var player = this.player;
if (player.ads._hasThereBeenALoadStartDuringPlayerLife) {
callback();
} else {
player.ads.debug('Waiting for loadstart...');
player.one('loadstart', function () {
player.ads.debug('Received loadstart event');
callback();
});
}
};
/*
* If there is no preroll, play content instead.
*/
Preroll.prototype.noPreroll = function noPreroll() {
var _this2 = this;
this.afterLoadStart(function () {
_this2.player.ads.debug('Skipping prerolls due to nopreroll event (Preroll)');
_this2.transitionTo(ContentPlayback);
});
};
/*
* Fire the readyforpreroll event. If loadstart hasn't happened yet,
* wait until loadstart first.
*/
Preroll.prototype.readyForPreroll = function readyForPreroll() {
var player = this.player;
this.afterLoadStart(function () {
player.ads.debug('Triggered readyforpreroll event (Preroll)');
player.trigger('readyforpreroll');
});
};
/*
* Don't allow the content to start playing while we're dealing with ads.
*/
Preroll.prototype.onPlay = function onPlay(player) {
player.ads.debug('Received play event (Preroll)');
if (!this.inAdBreak() && !this.isContentResuming()) {
cancelContentPlay(this.player);
}
};
/*
* adscanceled cancels all ads for the source. Play content now.
*/
Preroll.prototype.onAdsCanceled = function onAdsCanceled(player) {
var _this3 = this;
player.ads.debug('adscanceled (Preroll)');
this.afterLoadStart(function () {
_this3.transitionTo(ContentPlayback);
});
};
/*
* An ad error occured. Play content instead.
*/
Preroll.prototype.onAdsError = function onAdsError(player) {
var _this4 = this;
videojs$1.log('adserror (Preroll)');
// In the future, we may not want to do this automatically.
// Integrations should be able to choose to continue the ad break
// if there was an error.
if (this.inAdBreak()) {
player.ads.endLinearAdMode();
}
this.afterLoadStart(function () {
_this4.transitionTo(ContentPlayback);
});
};
/*
* Integration invoked startLinearAdMode, the ad break starts now.
*/
Preroll.prototype.startLinearAdMode = function startLinearAdMode() {
var player = this.player;
if (this.adsReady && !player.ads.inAdBreak() && !this.isContentResuming()) {
player.clearTimeout(this._timeout);
player.ads.adType = 'preroll';
obj.start(player);
} else {
videojs$1.log.warn('Unexpected startLinearAdMode invocation (Preroll)');
}
};
/*
* An ad has actually started playing.
* Remove the loading spinner.
*/
Preroll.prototype.onAdStarted = function onAdStarted(player) {
player.removeClass('vjs-ad-loading');
};
/*
* Integration invoked endLinearAdMode, the ad break ends now.
*/
Preroll.prototype.endLinearAdMode = function endLinearAdMode() {
var player = this.player;
if (this.inAdBreak()) {
player.removeClass('vjs-ad-loading');
obj.end(player);
this.contentResuming = true;
}
};
/*
* Ad skipped by integration. Play content instead.
*/
Preroll.prototype.skipLinearAdMode = function skipLinearAdMode() {
var _this5 = this;
var player = this.player;
if (player.ads.inAdBreak() || this.isContentResuming()) {
videojs$1.log.warn('Unexpected skipLinearAdMode invocation');
} else {
this.afterLoadStart(function () {
player.trigger('adskip');
player.ads.debug('skipLinearAdMode (Preroll)');
_this5.transitionTo(ContentPlayback);
});
}
};
/*
* Prerolls took too long! Play content instead.
*/
Preroll.prototype.onAdTimeout = function onAdTimeout(player) {
var _this6 = this;
this.afterLoadStart(function () {
player.ads.debug('adtimeout (Preroll)');
_this6.transitionTo(ContentPlayback);
});
};
/*
* Check if nopreroll event was too late before handling it.
*/
Preroll.prototype.onNoPreroll = function onNoPreroll(player) {
if (player.ads.inAdBreak() || this.isContentResuming()) {
videojs$1.log.warn('Unexpected nopreroll event (Preroll)');
} else {
this.noPreroll();
}
};
/*
* Cleanup timeouts and spinner.
*/
Preroll.prototype.cleanup = function cleanup() {
var player = this.player;
if (!player.ads._hasThereBeenALoadStartDuringPlayerLife) {
videojs$1.log.warn('Leaving Preroll state before loadstart event can cause issues.');
}
player.removeClass('vjs-ad-loading');
player.clearTimeout(this._timeout);
};
return Preroll;
}(AdState);
var Midroll = function (_AdState) {
inherits(Midroll, _AdState);
function Midroll() {
classCallCheck(this, Midroll);
return possibleConstructorReturn(this, _AdState.apply(this, arguments));
}
/*
* Midroll breaks happen when the integration calls startLinearAdMode,
* which can happen at any time during content playback.
*/
Midroll.prototype.init = function init(player) {
player.ads.adType = 'midroll';
obj.start(player);
};
/*
* Midroll break is done.
*/
Midroll.prototype.endLinearAdMode = function endLinearAdMode() {
var player = this.player;
if (this.inAdBreak()) {
this.contentResuming = true;
obj.end(player);
}
};
/*
* End midroll break if there is an error.
*/
Midroll.prototype.onAdsError = function onAdsError(player) {
// In the future, we may not want to do this automatically.
// Integrations should be able to choose to continue the ad break
// if there was an error.
if (this.inAdBreak()) {
player.ads.endLinearAdMode();
}
};
return Midroll;
}(AdState);
var Postroll = function (_AdState) {
inherits(Postroll, _AdState);
function Postroll() {
classCallCheck(this, Postroll);
return possibleConstructorReturn(this, _AdState.apply(this, arguments));
}
Postroll.prototype.init = function init(player) {
var _this2 = this;
// Legacy name that now simply means "handling postrolls".
player.ads._contentEnding = true;
// Start postroll process.
if (!player.ads.nopostroll_) {
player.addClass('vjs-ad-loading');
// Determine postroll timeout based on plugin settings
var timeout = player.ads.settings.timeout;
if (typeof player.ads.settings.postrollTimeout === 'number') {
timeout = player.ads.settings.postrollTimeout;
}
this._postrollTimeout = player.setTimeout(function () {
player.trigger('adtimeout');
}, timeout);
// No postroll, ads are done
} else {
player.setTimeout(function () {
player.ads.debug('Triggered ended event (no postroll)');
_this2.contentResuming = true;
player.trigger('ended');
}, 1);
}
};
/*
* Start the postroll if it's not too late.
*/
Postroll.prototype.startLinearAdMode = function startLinearAdMode() {
var player = this.player;
if (!player.ads.inAdBreak() && !this.isContentResuming()) {
player.ads.adType = 'postroll';
player.clearTimeout(this._postrollTimeout);
obj.start(player);
} else {
videojs$1.log.warn('Unexpected startLinearAdMode invocation (Postroll)');
}
};
/*
* An ad has actually started playing.
* Remove the loading spinner.
*/
Postroll.prototype.onAdStarted = function onAdStarted(player) {
player.removeClass('vjs-ad-loading');
};
Postroll.prototype.endLinearAdMode = function endLinearAdMode() {
var player = this.player;
if (this.inAdBreak()) {
player.removeClass('vjs-ad-loading');
obj.end(player);
this.contentResuming = true;
player.ads.debug('Triggered ended event (endLinearAdMode)');
player.trigger('ended');
}
};
Postroll.prototype.skipLinearAdMode = function skipLinearAdMode() {
var player = this.player;
if (player.ads.inAdBreak() || this.isContentResuming()) {
videojs$1.log.warn('Unexpected skipLinearAdMode invocation');
} else {
player.ads.debug('Postroll abort (skipLinearAdMode)');
player.trigger('adskip');
this.abort();
}
};
Postroll.prototype.onAdTimeout = function onAdTimeout(player) {
player.ads.debug('Postroll abort (adtimeout)');
this.abort();
};
Postroll.prototype.onAdsError = function onAdsError(player) {
player.ads.debug('Postroll abort (adserror)');
// In the future, we may not want to do this automatically.
// Integrations should be able to choose to continue the ad break
// if there was an error.
if (player.ads.inAdBreak()) {
player.ads.endLinearAdMode();
}
this.abort();
};
Postroll.prototype.onEnded = function onEnded() {
if (this.isContentResuming()) {
this.transitionTo(AdsDone);
} else {
videojs$1.log.warn('Unexpected ended event during postroll');
}
};
Postroll.prototype.onContentChanged = function onContentChanged(player) {
if (this.isContentResuming()) {
this.transitionTo(BeforePreroll);
} else if (!this.inAdBreak()) {
this.transitionTo(Preroll);
}
};
Postroll.prototype.onNoPostroll = function onNoPostroll(player) {
if (!this.isContentResuming() && !this.inAdBreak()) {
this.transitionTo(AdsDone);
} else {
videojs$1.log.warn('Unexpected nopostroll event (Postroll)');
}
};
Postroll.prototype.abort = function abort() {
var player = this.player;
this.contentResuming = true;
player.removeClass('vjs-ad-loading');
player.ads.debug('Triggered ended event (postroll abort)');
player.trigger('ended');
};
Postroll.prototype.cleanup = function cleanup() {
var player = this.player;
player.clearTimeout(this._postrollTimeout);
player.ads._contentEnding = false;
};
return Postroll;
}(AdState);
/*
* This is the initial state for a player with an ad plugin. Normally, it remains in this
* state until a "play" event is seen. After that, we enter the Preroll state to check for
* prerolls. This happens regardless of whether or not any prerolls ultimately will play.
* Errors and other conditions may lead us directly from here to ContentPlayback.
*/
var BeforePreroll = function (_ContentState) {
inherits(BeforePreroll, _ContentState);
function BeforePreroll() {
classCallCheck(this, BeforePreroll);
return possibleConstructorReturn(this, _ContentState.apply(this, arguments));
}
BeforePreroll.prototype.init = function init(player) {
this.adsReady = false;
};
/*
* The integration may trigger adsready before the play request. If so,
* we record that adsready already happened so the Preroll state will know.
*/
BeforePreroll.prototype.onAdsReady = function onAdsReady(player) {
player.ads.debug('Received adsready event (BeforePreroll)');
this.adsReady = true;
};
/*
* Ad mode officially begins on the play request, because at this point
* content playback is blocked by the ad plugin.
*/
BeforePreroll.prototype.onPlay = function onPlay(player) {
player.ads.debug('Received play event (BeforePreroll)');
// Don't start content playback yet
cancelContentPlay(player);
// Check for prerolls
this.transitionTo(Preroll, this.adsReady);
};
/*
* All ads for the entire video are canceled.
*/
BeforePreroll.prototype.onAdsCanceled = function onAdsCanceled(player) {
player.ads.debug('adscanceled (BeforePreroll)');
this.transitionTo(ContentPlayback);
};
/*
* An ad error occured. Play content instead.
*/
BeforePreroll.prototype.onAdsError = function onAdsError() {
this.transitionTo(ContentPlayback);
};
/*
* If there is no preroll, don't wait for a play event to move forward.
*/
BeforePreroll.prototype.onNoPreroll = function onNoPreroll() {
this.player.ads.debug('Skipping prerolls due to nopreroll event (BeforePreroll)');
this.transitionTo(ContentPlayback);
};
/*
* Prerolls skipped by integration. Play content instead.
*/
BeforePreroll.prototype.skipLinearAdMode = function skipLinearAdMode() {
var player = this.player;
player.trigger('adskip');
this.transitionTo(ContentPlayback);
};
/*
* Content source change before preroll is currently not handled. When
* developed, this is where to start.
*/
BeforePreroll.prototype.onContentChanged = function onContentChanged() {};
return BeforePreroll;
}(ContentState);
/*
* This state represents content playback the first time through before
* content ends. After content has ended once, we check for postrolls and
* move on to the AdsDone state rather than returning here.
*/
var ContentPlayback = function (_ContentState) {
inherits(ContentPlayback, _ContentState);
function ContentPlayback() {
classCallCheck(this, ContentPlayback);
return possibleConstructorReturn(this, _ContentState.apply(this, arguments));
}
ContentPlayback.prototype.init = function init(player) {
// Play the content if cancelContentPlay happened or we paused on 'contentupdate'
// and we haven't played yet. This happens if there was no preroll or if it
// errored, timed out, etc. Otherwise snapshot restore would play.
if (player.paused() && (player.ads._cancelledPlay || player.ads._pausedOnContentupdate)) {
player.play();
}
};
/*
* In the case of a timeout, adsready might come in late. This assumes the behavior
* that if an ad times out, it could still interrupt the content and start playing.
* An integration could behave otherwise by ignoring this event.
*/
ContentPlayback.prototype.onAdsReady = function onAdsReady(player) {
player.ads.debug('Received adsready event (ContentPlayback)');
if (!player.ads.nopreroll_) {
player.ads.debug('Triggered readyforpreroll event (ContentPlayback)');
player.trigger('readyforpreroll');
}
};
/*
* Content ended before postroll checks.
*/
ContentPlayback.prototype.onContentEnded = function onContentEnded(player) {
player.ads.debug('Received contentended event');
this.transitionTo(Postroll);
};
/*
* This is how midrolls start.
*/
ContentPlayback.prototype.startLinearAdMode = function startLinearAdMode() {
this.transitionTo(Midroll);
};
return ContentPlayback;
}(ContentState);
var AdsDone = function (_ContentState) {
inherits(AdsDone, _ContentState);
function AdsDone() {
classCallCheck(this, AdsDone);
return possibleConstructorReturn(this, _ContentState.apply(this, arguments));
}
AdsDone.prototype.init = function init(player) {
// From now on, `ended` events won't be redispatched
player.ads._contentHasEnded = true;
};
/*
* Midrolls do not play after ads are done.
*/
AdsDone.prototype.startLinearAdMode = function startLinearAdMode() {
videojs$1.log.warn('Unexpected startLinearAdMode invocation (AdsDone)');
};
return AdsDone;
}(ContentState);
/*
* This file is necessary to avoid this rollup issue:
* https://github.com/rollup/rollup/issues/1089
*/
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('AdState', {
beforeEach: function beforeEach() {
var _this = this;
this.player = {
ads: {}
};
this.adState = new AdState(this.player);
this.adState.transitionTo = function (newState) {
_this.newState = newState.name;
};
}
});
QUnit$1.test('does not start out with content resuming', function (assert) {
assert.equal(this.adState.contentResuming, false);
});
QUnit$1.test('is an ad state', function (assert) {
assert.equal(this.adState.isAdState(), true);
});
QUnit$1.test('transitions to ContentPlayback on playing if content resuming', function (assert) {
this.adState.contentResuming = true;
this.adState.onPlaying();
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('doesn\'t transition on playing if content not resuming', function (assert) {
this.adState.onPlaying();
assert.equal(this.newState, undefined, 'no transition');
});
QUnit$1.test('transitions to ContentPlayback on contentresumed if content resuming', function (assert) {
this.adState.contentResuming = true;
this.adState.onContentResumed();
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('doesn\'t transition on contentresumed if content not resuming', function (assert) {
this.adState.onContentResumed();
assert.equal(this.newState, undefined, 'no transition');
});
QUnit$1.test('can check if content is resuming', function (assert) {
assert.equal(this.adState.isContentResuming(), false, 'not resuming');
this.adState.contentResuming = true;
assert.equal(this.adState.isContentResuming(), true, 'resuming');
});
QUnit$1.test('can check if in ad break', function (assert) {
assert.equal(this.adState.inAdBreak(), false, 'not in ad break');
this.player.ads._inLinearAdMode = true;
assert.equal(this.adState.inAdBreak(), true, 'in ad break');
});
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('ContentState', {
beforeEach: function beforeEach() {
var _this = this;
this.player = {
ads: {
debug: function debug() {}
}
};
this.contentState = new ContentState(this.player);
this.contentState.transitionTo = function (newState) {
_this.newState = newState.name;
};
}
});
QUnit$1.test('is not an ad state', function (assert) {
assert.equal(this.contentState.isAdState(), false);
});
QUnit$1.test('handles content changed when not playing', function (assert) {
this.player.paused = function () {
return true;
};
this.player.pause = sinon.stub();
this.contentState.onContentChanged(this.player);
assert.equal(this.newState, 'BeforePreroll');
assert.equal(this.player.pause.callCount, 0, 'did not pause player');
assert.ok(!this.player.ads._pausedOnContentupdate, 'did not set _pausedOnContentupdate');
});
QUnit$1.test('handles content changed when playing', function (assert) {
this.player.paused = function () {
return false;
};
this.player.pause = sinon.stub();
this.contentState.onContentChanged(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.player.pause.callCount, 1, 'paused player');
assert.equal(this.player.ads._pausedOnContentupdate, true, 'set _pausedOnContentupdate');
});
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('State', {
beforeEach: function beforeEach() {
this.player = {
ads: {
debug: function debug() {}
}
};
this.state = new State(this.player);
}
});
QUnit$1.test('sets this.player', function (assert) {
assert.equal(this.state.player, this.player);
});
QUnit$1.test('can transition to another state', function (assert) {
var mockStateInit = false;
var MockState = function () {
function MockState() {
classCallCheck(this, MockState);
}
MockState.prototype.init = function init() {
mockStateInit = true;
};
return MockState;
}();
this.state.cleanup = sinon.stub();
this.state.transitionTo(MockState);
assert.ok(this.state.cleanup.calledOnce, 'cleaned up old state');
assert.equal(this.player.ads._state.constructor.name, 'MockState', 'set ads._state');
assert.equal(mockStateInit, true, 'initialized new state');
});
QUnit$1.test('throws error if isAdState is not implemented', function (assert) {
var error = void 0;
try {
this.state.isAdState();
} catch (e) {
error = e;
}
assert.equal(error.message, 'isAdState unimplemented for State');
});
QUnit$1.test('is not resuming content by default', function (assert) {
assert.equal(this.state.isContentResuming(), false);
});
QUnit$1.test('is not in an ad break by default', function (assert) {
assert.equal(this.state.inAdBreak(), false);
});
QUnit$1.test('handles events', function (assert) {
this.state.onPlay = sinon.stub();
this.state.handleEvent('play');
assert.ok(this.state.onPlay.calledOnce);
});
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('AdsDone', {
beforeEach: function beforeEach() {
this.player = {
ads: {}
};
this.adsDone = new AdsDone(this.player);
}
});
QUnit$1.test('sets _contentHasEnded on init', function (assert) {
this.adsDone.init(this.player);
assert.equal(this.player.ads._contentHasEnded, true, 'content has ended');
});
QUnit$1.test('does not play midrolls', function (assert) {
this.adsDone.transitionTo = sinon.spy();
this.adsDone.init(this.player);
this.adsDone.startLinearAdMode();
assert.equal(this.adsDone.transitionTo.callCount, 0, 'no transition');
});
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('BeforePreroll', {
beforeEach: function beforeEach() {
var _this = this;
this.events = [];
this.player = {
ads: {
debug: function debug() {}
},
setTimeout: function setTimeout() {},
trigger: function trigger(event) {
_this.events.push(event);
}
};
this.beforePreroll = new BeforePreroll(this.player);
this.beforePreroll.transitionTo = function (newState, arg) {
_this.newState = newState.name;
_this.transitionArg = arg;
};
this.cancelContentPlayStub = sinon.stub(CancelContentPlay, 'cancelContentPlay');
},
afterEach: function afterEach() {
this.cancelContentPlayStub.restore();
}
});
QUnit$1.test('transitions to Preroll (adsready first)', function (assert) {
this.beforePreroll.init();
assert.equal(this.beforePreroll.adsReady, false);
this.beforePreroll.onAdsReady(this.player);
assert.equal(this.beforePreroll.adsReady, true);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, true);
});
QUnit$1.test('transitions to Preroll (play first)', function (assert) {
this.beforePreroll.init();
assert.equal(this.beforePreroll.adsReady, false);
this.beforePreroll.onPlay(this.player);
assert.equal(this.newState, 'Preroll');
assert.equal(this.transitionArg, false);
});
QUnit$1.test('cancels ads', function (assert) {
this.beforePreroll.init();
this.beforePreroll.onAdsCanceled(this.player);
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('transitions to content playback on error', function (assert) {
this.beforePreroll.init();
this.beforePreroll.onAdsError(this.player);
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('has no preroll', function (assert) {
this.beforePreroll.init();
this.beforePreroll.onNoPreroll(this.player);
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('skips the preroll', function (assert) {
this.beforePreroll.init();
this.beforePreroll.skipLinearAdMode();
assert.equal(this.events[0], 'adskip');
assert.equal(this.newState, 'ContentPlayback');
});
QUnit$1.test('does nothing on content change', function (assert) {
this.beforePreroll.init();
this.beforePreroll.onContentChanged(this.player);
assert.equal(this.newState, undefined);
});
/*
* These tests are intended to be isolated unit tests for one state with all
* other modules mocked.
*/
QUnit$1.module('ContentPlayback', {
beforeEach: function beforeEach() {
var _this = this;
this.events = [];
this.playTriggered = false;
this.player = {
paused: function paused() {
return false;
},
play: function play() {
_this.playTriggered = true;
},
trigger: function trigger(event) {
_this.events.push(event);
},
ads: {
debug: function debug() {}
}
};
this.contentPlayback = new ContentPlayback(this.player);
this.contentPlayback.transitionTo = function (newState) {
_this.newState = newState.name;
};
}
});
QUnit$1.test('only plays on init on correct conditions', function (assert) {
this.player.paused = function () {
return false;
};
this.player.ads._cancelledPlay = false;
this.player.ads._pausedOnContentupdate = false;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, false);
this.player.paused = function () {
return true;
};
this.player.ads._cancelledPlay = false;
this.player.ads._pausedOnContentupdate = false;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, false);
this.player.paused = function () {
return false;
};
this.player.ads._cancelledPlay = true;
this.player.ads._pausedOnContentupdate = false;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, false);
this.player.paused = function () {
return false;
};
this.player.ads._cancelledPlay = false;
this.player.ads._pausedOnContentupdate = true;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, false);
this.player.paused = function () {
return true;
};
this.player.ads._cancelledPlay = true;
this.player.ads._pausedOnContentupdate = false;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, true);
this.player.paused = function () {
return true;
};
this.player.ads._cancelledPlay = false;
this.player.ads._pausedOnContentupdate = true;
this.contentPlayback.init(this.player);
assert.equal(this.playTriggered, true);
});
QUnit$1.test('adsready triggers readyforpreroll', function (assert) {
this.contentPlayback.init(this.player);
this.contentPlayback.onAdsReady(this.player);
assert.equal(this.events[0], 'readyforpreroll');
});
QUnit$1.test('no readyforpreroll if nopreroll_', function (assert) {
this.player.ads.nopreroll_ = true;
this.contentPlayback.init(this.player);
this.contentPlayback.onAdsReady(this.player);
assert.equal(this.events.length, 0, 'no events triggered');
});
QUnit$1.test('transitions to Postroll on contentended', function (assert) {
this.contentPlayback.init(this.player, false);
this.contentPlayback.onContentEnded(this.player);
assert.equal(this.newState, 'Postroll', 'transitioned to Postroll');
});
QUnit$1.test('transitions to Midroll on startlinearadmode', function (assert) {
this.contentPlayback.init(this.player, false);
this.contentPlayback.startLinearAdMode();
assert.equal(this.newState, 'Midroll', 'transitioned to Midroll');
});
/*
* These tests are intended t