videojs-contrib-ads
Version:
A framework that provides common functionality needed by video advertisement libraries working with video.js.
1,364 lines (1,142 loc) • 54.5 kB
JavaScript
/**
* videojs-contrib-ads
* @version 4.2.6
* @copyright 2017 Brightcove
* @license Apache-2.0
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribAds = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
exports['default'] = cancelContentPlay;
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
var _document = require('global/document');
var _document2 = _interopRequireDefault(_document);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function cancelContentPlay(player) {
if (player.ads.cancelPlayTimeout) {
// another cancellation is already in flight, so do nothing
return;
}
// Avoid content flash on non-iPad iOS and iPhones on iOS10 with playsinline
if (_video2['default'].browser.IS_IOS && _video2['default'].browser.IS_IPHONE && !player.el_.hasAttribute('playsinline')) {
(function () {
var width = player.currentWidth ? player.currentWidth() : player.width();
var height = player.currentHeight ? player.currentHeight() : player.height();
// A placeholder black box will be shown in the document while the player is hidden.
var placeholder = _document2['default'].createElement('div');
placeholder.style.width = width + 'px';
placeholder.style.height = height + 'px';
placeholder.style.background = 'black';
player.el_.parentNode.insertBefore(placeholder, player.el_);
// Hide the player. While in full-screen video playback mode on iOS, this
// makes the player show a black screen instead of content flash.
player.el_.style.display = 'none';
// Unhide the player and remove the placeholder once we're ready to move on.
player.one(['adstart', 'adtimeout', 'adserror', 'adscanceled', 'adskip', 'playing'], function () {
player.el_.style.display = 'block';
placeholder.remove();
});
// Detect fullscreen change, if returning from fullscreen and placeholder exists,
// remove placeholder and show player whether or not playsinline was attached.
player.on('fullscreenchange', function () {
if (placeholder && !player.isFullscreen()) {
player.el_.style.display = 'block';
placeholder.remove();
}
});
})();
}
// 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 = _window2['default'].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();
}
// When the 'content-playback' state is entered, this will let us know to play
player.ads.cancelledPlay = true;
}, 1);
} /*
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.
*/
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"global/document":8,"global/window":9}],2:[function(require,module,exports){
'use strict';
exports.__esModule = true;
exports['default'] = initializeContentupdate;
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
// Start sending contentupdate events
function initializeContentupdate(player) {
// 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();
// Check if a new src has been set, if so, trigger contentupdate
var checkSrc = function checkSrc() {
if (player.ads.state !== 'ad-playback') {
var 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
_window2['default'].setTimeout(checkSrc, 1);
} /*
This feature sends a `contentupdate` event when the player source changes.
*/
},{"global/window":9}],3:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
exports.processMetadataTracks = processMetadataTracks;
exports.setMetadataTrackMode = setMetadataTrackMode;
exports.getSupportedAdCue = getSupportedAdCue;
exports.getCueId = getCueId;
exports.processAdTrack = processAdTrack;
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/**
* This feature allows metadata text tracks to be manipulated once they are available,
* usually after the 'loadstart' event is observed on the player
* @param player A reference to a player
* @param processMetadataTrack A callback that performs some operations on a
* metadata text track
**/
function processMetadataTracks(player, processMetadataTrack) {
var tracks = player.textTracks();
var setModeAndProcess = function setModeAndProcess(track) {
if (track.kind === 'metadata') {
player.ads.cueTextTracks.setMetadataTrackMode(track);
processMetadataTrack(player, track);
}
};
// Text tracks are available
if (tracks.length > 0) {
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
setModeAndProcess(track);
}
// Wait until text tracks are added
// We avoid always setting the event handler in case
// integrations decide to handle this separately
// with a different handler for the same event
} else {
tracks.addEventListener('addtrack', function (event) {
var track = event.track;
setModeAndProcess(track);
});
}
}
/**
* Sets the track mode to one of 'disabled', 'hidden' or 'showing'
* @see https://github.com/videojs/video.js/blob/master/docs/guides/text-tracks.md
* Default behavior is to do nothing, @override if this is not desired
* @param track The text track to set the mode on
*/
/**
* This feature allows metadata text tracks to be manipulated once available
* @see processMetadataTracks.
* It also allows ad implementations to leverage ad cues coming through
* text tracks, @see processAdTrack
**/
function setMetadataTrackMode(track) {
return;
}
/**
* Determines whether cue is an ad cue and returns the cue data.
* @param player A reference to the player
* @param cue The cue to be checked
* Returns the given cue by default @override if futher processing is required
* @return the cueData in JSON if cue is a supported ad cue, or -1 if not
**/
function getSupportedAdCue(player, cue) {
return cue;
}
/**
* Gets the id associated with a cue.
* @param cue The cue to extract an ID from
* @returns The first occurance of 'id' in the object,
* @override if this is not the desired cue id
**/
function getCueId(player, cue) {
return cue.id;
}
/**
* Checks whether a cue has already been used
* @param cueId The Id associated with a cue
**/
var cueAlreadySeen = function cueAlreadySeen(player, cueId) {
return cueId !== undefined && player.ads.includedCues[cueId];
};
/**
* Indicates that a cue has been used
* @param cueId The Id associated with a cue
**/
var setCueAlreadySeen = function setCueAlreadySeen(player, cueId) {
if (cueId !== undefined && cueId !== '') {
player.ads.includedCues[cueId] = true;
}
};
/**
* This feature allows ad metadata tracks to be manipulated in ad implementations
* @param player A reference to the player
* @param cues The set of cues to work with
* @param processCue A method that uses a cue to make some
* ad request in the ad implementation
* @param [cancelAds] A method that dynamically cancels ads in the ad implementation
**/
function processAdTrack(player, cues, processCue, cancelAds) {
player.ads.includedCues = {};
// loop over set of cues
for (var i = 0; i < cues.length; i++) {
var cue = cues[i];
var cueData = this.getSupportedAdCue(player, cue);
// Exit if this is not a supported cue
if (cueData === -1) {
_video2['default'].log.warn('Skipping as this is not a supported ad cue.', cue);
return;
}
// Continue processing supported cue
var cueId = this.getCueId(player, cue);
var startTime = cue.startTime;
// Skip ad if cue was already used
if (cueAlreadySeen(player, cueId)) {
_video2['default'].log('Skipping ad already seen with ID ' + cueId);
return;
}
// Process cue as an ad cue
processCue(player, cueData, cueId, startTime);
// Indicate that this cue has been used
setCueAlreadySeen(player, cueId);
// Optional dynamic ad cancellation
if (cancelAds !== undefined) {
cancelAds(player, cueData);
}
}
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],4:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /*
This feature provides an optional method for ad integrations to insert run-time values
into an ad server URL or configuration.
*/
exports['default'] = adMacroReplacement;
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
var _document = require('global/document');
var _document2 = _interopRequireDefault(_document);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
// Return URI encoded version of value if uriEncode is true
var uriEncodeIfNeeded = function uriEncodeIfNeeded(value, uriEncode) {
if (uriEncode) {
return encodeURIComponent(value);
}
return value;
};
// Add custom field macros to macros object
// based on given name for custom fields property of mediainfo object.
var customFields = function customFields(mediainfo, macros, customFieldsName) {
if (mediainfo && mediainfo[customFieldsName]) {
var fields = mediainfo[customFieldsName];
var fieldNames = Object.keys(fields);
for (var i = 0; i < fieldNames.length; i++) {
var tag = '{mediainfo.' + customFieldsName + '.' + fieldNames[i] + '}';
macros[tag] = fields[fieldNames[i]];
}
}
};
// Public method that integrations use for ad macros.
// "string" is any string with macros to be replaced
// "uriEncode" if true will uri encode macro values when replaced
// "customMacros" is a object with custom macros and values to map them to
// - For example: {'{five}': 5}
// Return value is is "string" with macros replaced
// - For example: adMacroReplacement('{player.id}') returns a string of the player id
function adMacroReplacement(string, uriEncode, customMacros) {
if (uriEncode === undefined) {
uriEncode = false;
}
var macros = {};
if (customMacros !== undefined) {
macros = customMacros;
}
// Static macros
macros['{player.id}'] = this.options_['data-player'];
macros['{mediainfo.id}'] = this.mediainfo ? this.mediainfo.id : '';
macros['{mediainfo.name}'] = this.mediainfo ? this.mediainfo.name : '';
macros['{mediainfo.description}'] = this.mediainfo ? this.mediainfo.description : '';
macros['{mediainfo.tags}'] = this.mediainfo ? this.mediainfo.tags : '';
macros['{mediainfo.reference_id}'] = this.mediainfo ? this.mediainfo.reference_id : '';
macros['{mediainfo.duration}'] = this.mediainfo ? this.mediainfo.duration : '';
macros['{mediainfo.ad_keys}'] = this.mediainfo ? this.mediainfo.ad_keys : '';
macros['{player.duration}'] = this.duration();
macros['{timestamp}'] = new Date().getTime();
macros['{document.referrer}'] = _document2['default'].referrer;
macros['{window.location.href}'] = _window2['default'].location.href;
macros['{random}'] = Math.floor(Math.random() * 1000000000000);
// Custom fields in mediainfo
customFields(this.mediainfo, macros, 'custom_fields');
customFields(this.mediainfo, macros, 'customFields');
// Go through all the replacement macros and apply them to the string.
// This will replace all occurrences of the replacement macros.
for (var i in macros) {
string = string.split(i).join(uriEncodeIfNeeded(macros[i], uriEncode));
}
// Page variables
string = string.replace(/{pageVariable\.([^}]+)}/g, function (match, name) {
var value = void 0;
var context = _window2['default'];
var names = name.split('.');
// Iterate down multiple levels of selector without using eval
// This makes things like pageVariable.foo.bar work
for (var _i = 0; _i < names.length; _i++) {
if (_i === names.length - 1) {
value = context[names[_i]];
} else {
context = context[names[_i]];
}
}
var type = typeof value === 'undefined' ? 'undefined' : _typeof(value);
// Only allow certain types of values. Anything else is probably a mistake.
if (value === null) {
return 'null';
} else if (value === undefined) {
_video2['default'].log.warn('Page variable "' + name + '" not found');
return '';
} else if (type !== 'string' && type !== 'number' && type !== 'boolean') {
_video2['default'].log.warn('Page variable "' + name + '" is not a supported type');
return '';
}
return uriEncodeIfNeeded(String(value), uriEncode);
});
return string;
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"global/document":8,"global/window":9}],5:[function(require,module,exports){
'use strict';
exports.__esModule = true;
exports['default'] = redispatch;
/*
The goal of this feature is to make player events work as an integrator would
expect despite the presense of ads. For example, an integrator would expect
an `ended` event to happen once the content is ended. If an `ended` event is sent
as a result of an ad ending, that is a bug. The `redispatch` method should recognize
such `ended` events and prefix them so they are sent as `adended`, and so on with
all other player events.
*/
// Stop propogation for an event
var cancelEvent = function cancelEvent(player, event) {
// Pretend we called stopImmediatePropagation because we want the native
// element events to continue propagating
event.isImmediatePropagationStopped = function () {
return true;
};
event.cancelBubble = true;
event.isPropagationStopped = function () {
return true;
};
};
// Stop propogation for an event, then send a new event with the type of the original
// event with the given prefix added.
var prefixEvent = function prefixEvent(player, prefix, event) {
cancelEvent(player, event);
player.trigger({
type: prefix + event.type,
state: player.ads.state,
originalEvent: event
});
};
// Handle a player event, either by redispatching it with a prefix, or by
// letting it go on its way without any meddling.
function redispatch(event) {
// 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' && this.ads.state === 'preroll?') {
prefixEvent(this, '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' && this.ads.state === 'ad-playback' && !this.ads.videoElementRecycled()) {
prefixEvent(this, 'ad', event);
// If the ad takes a long time to load, "playing" caused by play/pause can happen
// during "ads-ready?" instead of "preroll?" or "ad-playback", skipping the
// other conditions that would normally catch it
} else if (event.type === 'playing' && this.ads.state === 'ads-ready?') {
prefixEvent(this, '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' && this.ads.state === 'ad-playback' && this.ads.videoElementRecycled()) {
cancelEvent(this, 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 (this.ads.state === 'ad-playback') {
if (this.ads.videoElementRecycled() || this.ads.stitchedAds()) {
prefixEvent(this, 'ad', event);
}
// Send contentended if ended happens during content.
// We will make sure an ended event is sent after postrolls.
} else if (this.ads.state === 'content-playback' && event.type === 'ended') {
prefixEvent(this, 'content', event);
// Event prefixing during content resuming is complicated
} else if (this.ads.state === 'content-resuming') {
// 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 (this.ads.snapshot && this.currentSrc() !== this.ads.snapshot.currentSrc) {
// Don't prefix `loadstart` event
if (event.type === 'loadstart') {
return;
}
// All other events get "content" prefix
return prefixEvent(this, 'content', event);
// Content resuming after postroll
} else if (this.ads.snapshot && this.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 prefixEvent(this, 'content', event);
}
// Content resuming after preroll or midroll
// Events besides "playing" get "content" prefix
if (event.type !== 'playing') {
prefixEvent(this, 'content', event);
}
}
}
},{}],6:[function(require,module,exports){
(function (global){
'use strict';
exports.__esModule = true;
exports.getPlayerSnapshot = getPlayerSnapshot;
exports.restorePlayerSnapshot = restorePlayerSnapshot;
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/**
* 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
*/
/*
The snapshot feature is responsible for saving the player state before an ad, then
restoring the player state after an ad.
*/
function getPlayerSnapshot(player) {
var currentTime = void 0;
if (_video2['default'].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 remoteTracks = player.remoteTextTracks ? player.remoteTextTracks() : [];
var tracks = player.textTracks ? player.textTracks() : [];
var suppressedRemoteTracks = [];
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 < remoteTracks.length; i++) {
var track = remoteTracks[i];
suppressedRemoteTracks.push({
track: track,
mode: track.mode
});
track.mode = 'disabled';
}
snapshotObject.suppressedRemoteTracks = suppressedRemoteTracks;
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 suppressedRemoteTracks = snapshotObject.suppressedRemoteTracks;
var suppressedTracks = snapshotObject.suppressedTracks;
var trackSnapshot = void 0;
var restoreTracks = function restoreTracks() {
for (var i = 0; i < suppressedRemoteTracks.length; i++) {
trackSnapshot = suppressedRemoteTracks[i];
trackSnapshot.track.mode = trackSnapshot.mode;
}
for (var _i2 = 0; _i2 < suppressedTracks.length; _i2++) {
trackSnapshot = suppressedTracks[_i2];
trackSnapshot.track.mode = trackSnapshot.mode;
}
};
// finish restoring the playback state
var resume = function resume() {
var currentTime = void 0;
if (_video2['default'].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();
}
};
// 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--) {
_window2['default'].setTimeout(tryToResume, 50);
} else {
try {
resume();
} catch (e) {
_video2['default'].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);
// if the src changed for ad playback, reset it
player.src({ src: snapshotObject.currentSrc, type: snapshotObject.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() || !snapshotObject.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();
}
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"global/window":9}],7:[function(require,module,exports){
},{}],8:[function(require,module,exports){
(function (global){
var topLevel = typeof global !== 'undefined' ? global :
typeof window !== 'undefined' ? window : {}
var minDoc = require('min-document');
if (typeof document !== 'undefined') {
module.exports = document;
} else {
var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
if (!doccy) {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
module.exports = doccy;
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"min-document":7}],9:[function(require,module,exports){
(function (global){
if (typeof window !== "undefined") {
module.exports = window;
} else if (typeof global !== "undefined") {
module.exports = global;
} else if (typeof self !== "undefined"){
module.exports = self;
} else {
module.exports = {};
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],10:[function(require,module,exports){
(function (global){
'use strict';
var _window = require('global/window');
var _window2 = _interopRequireDefault(_window);
var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _video2 = _interopRequireDefault(_video);
var _redispatch = require('./redispatch.js');
var _redispatch2 = _interopRequireDefault(_redispatch);
var _snapshot = require('./snapshot.js');
var snapshot = _interopRequireWildcard(_snapshot);
var _contentupdate = require('./contentupdate.js');
var _contentupdate2 = _interopRequireDefault(_contentupdate);
var _cancelContentPlay = require('./cancelContentPlay.js');
var _cancelContentPlay2 = _interopRequireDefault(_cancelContentPlay);
var _macros = require('./macros.js');
var _macros2 = _interopRequireDefault(_macros);
var _cueTextTracks = require('./cueTextTracks.js');
var cueTextTracks = _interopRequireWildcard(_cueTextTracks);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/*
This main plugin file is responsible for integration logic and enabling the features
that live in in separate files.
*/
var VIDEO_EVENTS = _video2['default'].getTech('Html5').Events;
/**
* 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
*/
var removeNativePoster = function removeNativePoster(player) {
var tech = player.$('.vjs-tech');
if (tech) {
tech.removeAttribute('poster');
}
};
// ---------------------------------------------------------------------------
// Ad Framework
// ---------------------------------------------------------------------------
// default framework settings
var 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,
// set this to true when using ads that are part of the content video
stitchedAds: false
};
var contribAdsPlugin = function contribAdsPlugin(options) {
var player = this; // eslint-disable-line consistent-this
var settings = _video2['default'].mergeOptions(defaults, options);
// 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
var videoEvents = VIDEO_EVENTS.concat(['firstplay', 'loadedalldata', 'playing']);
// Set up redispatching of player events
player.on(videoEvents, _redispatch2['default']);
// "vjs-has-started" should be present at the end of a video. This makes sure it's
// always there.
player.on('ended', function () {
if (!player.hasClass('vjs-has-started')) {
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 prevent 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;
});
// Remove ad-loading class when ad plays or when content plays (in case there was no ad)
// If you remove this class too soon you can get a flash of content!
player.on(['ads-ad-started', 'playing'], function () {
player.removeClass('vjs-ad-loading');
});
// Replace the plugin constructor with the ad namespace
player.ads = {
state: 'content-set',
disableNextSnapshotRestore: false,
// This is set to true if the content has ended once. After that, the user can
// seek backwards and replay content, but _contentHasEnded remains true.
_contentHasEnded: false,
// This is an estimation of the current ad type being played
// This is experimental currently. Do not rely on its presence or behavior!
adType: null,
VERSION: '4.2.6',
reset: function reset() {
player.ads.disableNextSnapshotRestore = false;
player.ads._contentHasEnded = false;
player.ads.snapshot = null;
player.ads.adType = null;
},
// Call this when an ad response has been received and there are
// linear ads ready to be played.
startLinearAdMode: function startLinearAdMode() {
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 endLinearAdMode() {
if (player.ads.state === 'ad-playback') {
player.trigger('adend');
// In the case of an empty ad response, we want to make sure that
// the vjs-ad-loading class is always removed. We could probably check for
// duration on adPlayer for an empty ad but we remove it here just to make sure
player.removeClass('vjs-ad-loading');
}
},
// 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 skipLinearAdMode() {
if (player.ads.state !== 'ad-playback') {
player.trigger('adskip');
}
},
stitchedAds: function stitchedAds(arg) {
if (arg !== undefined) {
this._stitchedAds = !!arg;
}
return this._stitchedAds;
},
// Returns whether the video element has been modified since the
// snapshot was taken.
// We test both src and currentSrc because changing the src attribute to a URL that
// AdBlocker is intercepting doesn't update currentSrc.
videoElementRecycled: function videoElementRecycled() {
if (!this.snapshot) {
throw new Error('You cannot use videoElementRecycled while there is no snapshot.');
}
var srcChanged = player.tech_.src() !== this.snapshot.src;
var currentSrcChanged = player.currentSrc() !== this.snapshot.currentSrc;
return srcChanged || currentSrcChanged;
},
// Returns a boolean indicating if given player is in live mode.
// Can be replaced when this is fixed: https://github.com/videojs/video.js/issues/3262
isLive: function isLive(somePlayer) {
if (somePlayer.duration() === Infinity) {
return true;
} else if (_video2['default'].browser.IOS_VERSION === '8' && somePlayer.duration() === 0) {
return true;
}
return false;
},
// Return true if content playback should mute and continue during ad breaks.
// This is only done during live streams on platforms where it's supported.
// This improves speed and accuracy when returning from an ad break.
shouldPlayContentBehindAd: function shouldPlayContentBehindAd(somePlayer) {
return !_video2['default'].browser.IS_IOS && !_video2['default'].browser.IS_ANDROID && somePlayer.duration() === Infinity;
}
};
player.ads.stitchedAds(settings.stitchedAds);
player.ads.cueTextTracks = cueTextTracks;
player.ads.adMacroReplacement = _macros2['default'].bind(player);
// Start sending contentupdate events for this player
(0, _contentupdate2['default'])(player);
// Global contentupdate handler for resetting plugin state
player.on('contentupdate', player.ads.reset);
// Ad Playback State Machine
var states = {
'content-set': {
events: {
adscanceled: function adscanceled() {
this.state = 'content-playback';
},
adsready: function adsready() {
this.state = 'ads-ready';
},
play: function play() {
this.state = 'ads-ready?';
(0, _cancelContentPlay2['default'])(player);
// remove the poster so it doesn't flash between videos
removeNativePoster(player);
},
adserror: function adserror() {
this.state = 'content-playback';
},
adskip: function adskip() {
this.state = 'content-playback';
}
}
},
'ads-ready': {
events: {
play: function play() {
this.state = 'preroll?';
(0, _cancelContentPlay2['default'])(player);
},
adskip: function adskip() {
this.state = 'content-playback';
},
adserror: function adserror() {
this.state = 'content-playback';
}
}
},
'preroll?': {
enter: function enter() {
if (player.ads.nopreroll_) {
// This will start the ads manager in case there are later ads
player.trigger('readyforpreroll');
// If we don't wait a tick, entering content-playback will cancel
// cancelPlayTimeout, causing the video to not pause for the ad
_window2['default'].setTimeout(function () {
// Don't wait for a preroll
player.trigger('nopreroll');
}, 1);
} 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 = _window2['default'].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 leave() {
_window2['default'].clearTimeout(player.ads.adTimeoutTimeout);
},
events: {
play: function play() {
(0, _cancelContentPlay2['default'])(player);
},
adstart: function adstart() {
this.state = 'ad-playback';
player.ads.adType = 'preroll';
},
adskip: function adskip() {
this.state = 'content-playback';
},
adtimeout: function adtimeout() {
this.state = 'content-playback';
},
adserror: function adserror() {
this.state = 'content-playback';
},
nopreroll: function nopreroll() {
this.state = 'content-playback';
}
}
},
'ads-ready?': {
enter: function enter() {
player.addClass('vjs-ad-loading');
player.ads.adTimeoutTimeout = _window2['default'].setTimeout(function () {
player.trigger('adtimeout');
}, settings.timeout);
},
leave: function leave() {
_window2['default'].clearTimeout(player.ads.adTimeoutTimeout);
player.removeClass('vjs-ad-loading');
},
events: {
play: function play() {
(0, _cancelContentPlay2['default'])(player);
},
adscanceled: function adscanceled() {
this.state = 'content-playback';
},
adsready: function adsready() {
this.state = 'preroll?';
},
adskip: function adskip() {
this.state = 'content-playback';
},
adtimeout: function adtimeout() {
this.state = 'content-playback';
},
adserror: function adserror() {
this.state = 'content-playback';
}
}
},
'ad-playback': {
enter: function enter() {
// capture current player state snapshot (playing, currentTime, src)
if (!player.ads.shouldPlayContentBehindAd(player)) {
this.snapshot = snapshot.getPlayerSnapshot(player);
}
// Mute the player behind the ad
if (player.ads.shouldPlayContentBehindAd(player)) {
this.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');
}
// 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) {
// If we don't wait a tick, we could cancel the pause for cancelContentPlay,
// resulting in content playback behind the ad
_window2['default'].setTimeout(function () {
_window2['default'].clearTimeout(player.ads.cancelPlayTimeout);
player.ads.cancelPlayTimeout = null;
}, 1);
}
},
leave: function leave() {
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)) {
snapshot.restorePlayerSnapshot(player, this.snapshot);
}
// Reset the volume to pre-ad levels
if (player.ads.shouldPlayContentBehindAd(player)) {
player.volume(this.preAdVolume_);
}
},
events: {
adend: function adend() {
this.state = 'content-resuming';
player.ads.adType = null;
},
adserror: function adserror() {
this.state = 'content-resuming';
// Trigger 'adend' to notify that we are exiting 'ad-playback'
player.trigger('adend');
}
}
},
'content-resuming': {
enter: function enter() {
if (this._contentHasEnded) {
_window2['default'].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 = _window2['default'].setTimeout(function () {
player.trigger('ended');
}, 1000);
}
},
leave: function leave() {
_window2['default'].clearTimeout(player.ads._fireEndedTimeout);
},
events: {
contentupdate: function contentupdate() {
this.state = 'content-set';
},
// This is for stitched ads only.
contentresumed: function contentresumed() {
this.state = 'content-playback';
},
playing: function playing() {
this.state = 'content-playback';
},
ended: function ended() {
this.state = 'content-playback';
}
}
},
'postroll?': {
enter: function enter() {
this.snapshot = snapshot.getPlayerSnapshot(player);
if (player.ads.nopostroll_) {
_window2['default'].setTimeout(function () {
// content-resuming happens after the timeout for backward-compatibility
// with plugins that relied on a postrollTimeout before nopostroll was
// implemented
player.ads.state = 'content-resuming';
player.trigger('ended');
}, 1);
} else {
player.addClass('vjs-ad-loading');
player.ads.adTimeoutTimeout = _window2['default'].setTimeout(function () {
player.trigger('adtimeout');
}, settings.postrollTimeout);
}
},
leave: function leave() {
_window2['default'].clearTimeout(player.ads.adTimeoutTimeout);
player.removeClass('vjs-ad-loading');
},
events: {
adstart: function adstart() {
this.state = 'ad-playback';
player.ads.adType = 'postroll';
},
adskip: function adskip() {
this.state = 'content-resuming';
_window2['default'].setTimeout(function () {
player.trigger('ended');
}, 1);
},
adtimeout: function adtimeout() {
this.state = 'content-resuming';
_window2['default'].setTimeout(function () {
player.trigger('ended');
}, 1);
},
adserror: function adserror() {
this.state = 'content-resuming';
_window2['default'].setTimeout(function () {
player.trigger('ended');
}, 1);
},
contentupdate: function contentupdate() {
this.state = 'ads-ready?';
}
}
},
'content-playback': {
enter: function enter() {
// make sure that any cancelPlayTimeout is cleared
if (player.ads.cancelPlayTimeout) {
_window2['default'].clearTimeout(player.ads.cancelPlayTimeout);
player.ads.cancelPlayTimeout = null;
}
// This was removed because now that "playing" is fixed to only play after
// preroll, any integration should just use the "playing" event. However,
// we found out some 3rd party code relied on this event, so we've temporarily
// added it back in to give people more time to update their code.
player.trigger({
type: 'contentplayback',
triggerevent: player.ads.triggerevent
});
// Play the content
if (player.ads.cancelledPlay) {
player.ads.cancelledPlay = false;