ads-manager
Version:
HTML5 Video Ads Manager based on @dailymotion/vast-client
1,277 lines (1,162 loc) • 43.9 kB
JavaScript
import { VASTClient, VASTParser, VASTTracker } from '@dailymotion/vast-client';
import AdError from './ad-error';
import Ad from './ad';
const AdsManager = function(adContainer) {
if(!(adContainer && (adContainer instanceof HTMLElement
|| adContainer.getRootNode))) {
throw new Error('ad container is not defined');
}
// Ad Container
this._adContainer = adContainer;
// Own Slot
this._ownSlot = null;
// Video Slot
this._videoSlot = null;
// Slot
this._slot = null;
// Create Own Slot
this.createOwnSlot();
// Events
this.EVENTS = {
AdsManagerLoaded: 'AdsManagerLoaded', // After success ad request, when vast xml is parsed and ready
AdStarted: 'AdStarted',
AdStopped: 'AdStopped',
AdSkipped: 'AdSkipped',
AdLoaded: 'AdLoaded',
AdLinearChange: 'AdLinearChange',
AdSizeChange: 'AdSizeChange',
AdExpandedChange: 'AdExpandedChange',
AdSkippableStateChange: 'AdSkippableStateChange',
AdDurationChange: 'AdDurationChange',
AdRemainingTimeChange: 'AdRemainingTimeChange',
AdVolumeChange: 'AdVolumeChange',
AdImpression: 'AdImpression',
AdClickThru: 'AdClickThru',
AdInteraction: 'AdInteraction',
AdVideoStart: 'AdVideoStart',
AdVideoFirstQuartile: 'AdVideoFirstQuartile',
AdVideoMidpoint: 'AdVideoMidpoint',
AdVideoThirdQuartile: 'AdVideoThirdQuartile',
AdVideoComplete: 'AdVideoComplete',
AdUserAcceptInvitation: 'AdUserAcceptInvitation',
AdUserMinimize: 'AdUserMinimize',
AdUserClose: 'AdUserClose',
AdPaused: 'AdPaused',
AdPlaying: 'AdPlaying',
AdError: 'AdError',
AdLog: 'AdLog',
AllAdsCompleted: 'AllAdsCompleted' // After all ads completed, vast, vpaid, vmap
};
this._eventCallbacks = {};
this._creativeEventCallbacks = {};
// Attributes
this._attributes = {
width: 300,
height: 154,
viewMode: 'normal',
desiredBitrate: -1, // 268,
duration: 10,
remainingTime: 10,
currentTime: 0,
volume: 0,
version: '!!#Version#!!'
};
// Quartile Events
this._quartileEvents = [
{ event: 'AdImpression', value: 0 },
{ event: 'AdVideoStart', value: 0 },
{ event: 'AdVideoFirstQuartile', value: 25 },
{ event: 'AdVideoMidpoint', value: 50 },
{ event: 'AdVideoThirdQuartile', value: 75 },
{ event: 'AdVideoComplete', value: 100 }
];
this._nextQuartileIndex = 0;
this._defaultEventCallbacks = {
'AdImpression': this.onAdImpression.bind(this),
'AdVideoStart': this.onAdVideoStart.bind(this),
'AdVideoFirstQuartile': this.onAdVideoFirstQuartile.bind(this),
'AdVideoMidpoint': this.onAdVideoMidpoint.bind(this),
'AdVideoThirdQuartile': this.onAdVideoThirdQuartile.bind(this),
'AdVideoComplete': this.onAdVideoComplete.bind(this)
};
// Options
this._options = {
autoplay: true,
muted: true,
vastLoadTimeout: 23000,
loadVideoTimeout: 8000,
withCredentials: false,
wrapperLimit: 10,
resolveAll: true
};
// Error codes
this.ERROR_CODES = {
VAST_MALFORMED_RESPONSE:100,
ADS_REQUEST_NETWORK_ERROR: 1012,
FAILED_TO_REQUEST_ADS: 1005,
UNKNOWN_AD_RESPONSE: 1010,
VAST_ASSET_NOT_FOUND: 1007,
VAST_EMPTY_RESPONSE: 1009,
VAST_LINEAR_ASSET_MISMATCH: 403,
VAST_LOAD_TIMEOUT: 301,
VAST_MEDIA_LOAD_TIMEOUT: 402,
VIDEO_PLAY_ERROR: 400,
VPAID_ERROR: 901,
};
// Error messages
this.ERROR_MESSAGES = {
VAST_MALFORMED_RESPONSE: 'VAST response was malformed and could not be parsed.', // 100
ADS_REQUEST_ERROR: 'Unable to request ads from server. Cause: {0}.', // 1005
ADS_REQUEST_NETWORK_ERROR: 'Unable to request ads from server due to network error.', // 1012
FAILED_TO_REQUEST_ADS: 'The was a problem requesting ads from the server.', // 1005
NO_ADS_FOUND: 'The response does not contain any valid ads.', // 1009
UNKNOWN_AD_RESPONSE: 'The ad response was not understood and cannot be parsed.', // 1010
VAST_ASSET_NOT_FOUND: 'No assets were found in the VAST ad response.', // 1007
VAST_EMPTY_RESPONSE: 'The VAST response document is empty.', // 1009
VAST_LINEAR_ASSET_MISMATCH: 'Linear assets were found in the VAST ad response, but none of them matched the player\'s capabilities.', // 403
VAST_LOAD_TIMEOUT: 'Ad request reached a timeout.', // 301
VAST_MEDIA_LOAD_TIMEOUT: 'VAST media file loading reached a timeout of {0} seconds.', // 402
VIDEO_PLAY_ERROR: 'There was an error playing the video ad.', // 400
VPAID_CREATIVE_ERROR: 'An unexpected error occurred within the VPAID creative. Refer to the inner error for more info.' // 901 TODO:
};
// Errors
this.ERRORS = {
VAST_MALFORMED_RESPONSE: new AdError(this.ERROR_MESSAGES.VAST_MALFORMED_RESPONSE, this.ERROR_CODES.VAST_MALFORMED_RESPONSE),
VAST_EMPTY_RESPONSE: new AdError(this.ERROR_MESSAGES.VAST_EMPTY_RESPONSE, this.ERROR_CODES.VAST_EMPTY_RESPONSE),
VAST_ASSET_NOT_FOUND: new AdError(this.ERROR_MESSAGES.VAST_ASSET_NOT_FOUND, this.ERROR_CODES.VAST_ASSET_NOT_FOUND),
VAST_LINEAR_ASSET_MISMATCH: new AdError(this.ERROR_MESSAGES.VAST_LINEAR_ASSET_MISMATCH, this.ERROR_CODES.VAST_LINEAR_ASSET_MISMATCH),
VAST_LOAD_TIMEOUT: new AdError(this.ERROR_MESSAGES.VAST_LOAD_TIMEOUT, this.ERROR_CODES.VAST_LOAD_TIMEOUT),
VAST_MEDIA_LOAD_TIMEOUT: new AdError(this.ERROR_MESSAGES.VAST_MEDIA_LOAD_TIMEOUT, this.ERROR_CODES.VAST_MEDIA_LOAD_TIMEOUT),
VIDEO_PLAY_ERROR: new AdError(this.ERROR_MESSAGES.VIDEO_PLAY_ERROR, this.ERROR_CODES.VIDEO_PLAY_ERROR),
VPAID_CREATIVE_ERROR: new AdError(this.ERROR_MESSAGES.VPAID_CREATIVE_ERROR, this.ERROR_CODES.VPAID_ERROR)
};
this._vastClient = null;
this._vastParser = null;
this._vastTracker = null;
this._ad = null;
this._adPod = null;
this._isAdPod = false;
this._creative = null;
this._mediaFiles = null;
this._mediaFileIndex = 0;
this._mediaFile = null;
this._isVPAID = false;
this._vpaidIframe = null;
this._vpaidCreative = null;
// Timers, Intervals
this._vastMediaLoadTimer = null;
this._vpaidProgressTimer = null;
// Request ID
this._requestId = null;
// Handlers
this._handleLoadCreativeMessage = this.handleLoadCreativeMessage.bind(this);
// Slot handlers
this._handleSlotClick = this.handleSlotClick.bind(this);
// Video slot handlers
this._handleVideoSlotError = this.handleVideoSlotError.bind(this);
this._handleVideoSlotCanPlay = this.handleVideoSlotCanPlay.bind(this);
this._handleVideoSlotVolumeChange = this.handleVideoSlotVolumeChange.bind(this);
this._handleVideoSlotTimeUpdate = this.handleVideoSlotTimeUpdate.bind(this);
this._handleVideoSlotLoadedMetaData = this.handleVideoSlotLoadedMetaData.bind(this);
this._handleVideoSlotEnded = this.handleVideoSlotEnded.bind(this);
this.MIN_VPAID_VERSION = 2;
this._hasLoaded = false;
this._hasError = false;
this._hasImpression = false;
this._hasStarted = false;
this._isDestroyed = false;
};
AdsManager.prototype.createOwnSlot = function() {
console.log('create slot......');
this._ownSlot = document.createElement('div');
this._ownSlot.style.position = 'absolute';
//this._slot.style.display = 'none';
this._adContainer.appendChild(this._ownSlot);
this.createVideoSlot();
this.createSlot();
};
AdsManager.prototype.removeSlot = function() {
this._ownSlot.parentNode && this._ownSlot.parentNode.removeChild(this._ownSlot);
};
AdsManager.prototype.showSlot = function() {
// Check if video slot has src, if no then hide video slot
if(this._videoSlot.src === '') {
this.hideVideoSlot();
}
// Show slot
this._ownSlot.style.display = 'block';
};
AdsManager.prototype.hideSlot = function() {
// Hide slot
this._ownSlot.style.display = 'none';
};
AdsManager.prototype.resizeSlot = function(width, height) {
this._ownSlot.style.width = width + 'px';
this._ownSlot.style.height = height + 'px';
};
AdsManager.prototype.createVideoSlot = function() {
this._videoSlot = document.createElement('video');
this._videoSlot.setAttribute('webkit-playsinline', true);
this._videoSlot.setAttribute('playsinline', true);
//this._videoSlot.setAttribute('preload', 'none');
this._videoSlot.style.width = '100%';
this._videoSlot.style.height = '100%';
this._videoSlot.style.backgroundColor = 'rgb(0, 0, 0)';
this._videoSlot.style.display = 'none';
const that = this;
// Overwrite getter/setter of src
const _src = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'src');
Object.defineProperty(this._videoSlot, 'src', {
set: function(value) {
_src.set.call(this, value);
console.log('video src changed', value);
that.showHideVideoSlot();
},
get: _src.get
});
// Override setAttribute function
const _setAttribute = this._videoSlot.setAttribute;
this._videoSlot.setAttribute = function() {
const value = _setAttribute.apply(this, [].slice.call(arguments))
if(arguments[0] === 'src') {
console.log('video setAttribute src', arguments[1]);
that.showHideVideoSlot();
}
return value;
}
//this._adContainer.appendChild(this._videoSlot);
this._ownSlot.appendChild(this._videoSlot);
};
AdsManager.prototype.createSlot = function() {
this._slot = document.createElement('div');
this._slot.style.position = 'absolute';
this._slot.style.top = '0px';
this._slot.style.left = '0px';
this._slot.style.right = '0px';
this._slot.style.bottom = '0px';
this._ownSlot.appendChild(this._slot);
};
AdsManager.prototype.hideVideoSlot = function() {
console.log('hide video slot');
this._videoSlot.style.display = 'none';
};
AdsManager.prototype.showVideoSlot = function() {
console.log('show video slot');
this._videoSlot.style.display = 'block';
};
AdsManager.prototype.showHideVideoSlot = function() {
if(this._videoSlot.getAttribute('src') === '') {
this.hideVideoSlot();
} else {
this.showVideoSlot();
}
};
AdsManager.prototype.stopVASTMediaLoadTimeout = function() {
if(this._vastMediaLoadTimer) {
clearTimeout(this._vastMediaLoadTimer);
this._vastMediaLoadTimer = null;
}
};
AdsManager.prototype.startVASTMediaLoadTimeout = function() {
this.stopVASTMediaLoadTimeout();
this._vastMediaLoadTimer = setTimeout(() => {
this.onAdError(this.ERRORS.VAST_MEDIA_LOAD_TIMEOUT.formatMessage(Math.floor((this._options.loadVideoTimeout / 1000) % 60)));
}, this._options.loadVideoTimeout);
};
AdsManager.prototype.updateVPAIDProgress = function() {
// Check remaining time
this._attributes.remainingTime = this._isCreativeFunctionInvokable('getAdRemainingTime') ? this._vpaidCreative.getAdRemainingTime() : -1;
if(!isNaN(this._attributes.remainingTime) && this._attributes.remainingTime !== 1) {
this._attributes.currentTime = this._attributes.duration - this._attributes.remainingTime;
// Track progress
this._vastTracker.setProgress(this._attributes.currentTime);
}
};
AdsManager.prototype.startVPAIDProgress = function() {
this.stopVPAIDProgress();
this._vpaidProgressTimer = setInterval(() => {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this.updateVPAIDProgress();
} else {
this.stopVPAIDProgress();
}
}, 1000);
};
AdsManager.prototype.stopVPAIDProgress = function() {
if(this._vpaidProgressTimer) {
clearInterval(this._vpaidProgressTimer);
this._vpaidProgressTimer = null;
}
};
AdsManager.prototype._callEvent = function(eventName) {
if(eventName in this._eventCallbacks) {
this._eventCallbacks[eventName]();
}
};
AdsManager.prototype.addEventListener = function(eventName, callback, context) {
const givenCallback = callback.bind(context);
this._eventCallbacks[eventName] = givenCallback;
};
AdsManager.prototype.removeEventListener = function(eventName) {
this._eventCallbacks[eventName] = null;
};
AdsManager.prototype.removeEventListeners = function(eventCallbacks) {
for (const eventName in eventCallbacks) {
eventCallbacks.hasOwnProperty(eventName) && this.removeEventListener(eventName);
}
};
AdsManager.prototype.onAdsManagerLoaded = function() {
this._callEvent(this.EVENTS.AdsManagerLoaded);
};
AdsManager.prototype.onAdLoaded = function() {
this._hasLoaded = true;
this.stopVASTMediaLoadTimeout();
if (this.EVENTS.AdLoaded in this._eventCallbacks) {
this._eventCallbacks[this.EVENTS.AdLoaded](new Ad(this._creative));
}
};
AdsManager.prototype.onAdDurationChange = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this._attributes.duration = this._isCreativeFunctionInvokable('getAdDuration') ? this._vpaidCreative.getAdDuration() : -1;
if(this._attributes.duration !== -1) {
this._vastTracker.setDuration(this._attributes.duration);
}
}
this._callEvent(this.EVENTS.AdDurationChange);
};
AdsManager.prototype.onAdSizeChange = function() {
this._callEvent(this.EVENTS.AdSizeChange);
};
AdsManager.prototype.onAdStarted = function() {
this._hasStarted = true;
this._callEvent(this.EVENTS.AdStarted);
};
AdsManager.prototype.onAdVideoStart = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this.updateVPAIDProgress();
}
this._callEvent(this.EVENTS.AdVideoStart);
};
AdsManager.prototype.onAdStopped = function() {
if(!this._hasStarted) {
this.onAdError(this.ERRORS.VPAID_CREATIVE_ERROR);
} else {
if(this._isAdPod && this._adPod.length != 0) {
this._nextQuartileIndex = 0;
this._hasImpression = false;
// Removes ad assets loaded at runtime that need to be properly removed at the time of ad completion
// and stops the ad and all tracking.
window.removeEventListener('message', this._handleLoadCreativeMessage);
if(this._isVPAID) {
// Unsubscribe for VPAID events
this.removeCallbacksForCreative(this._creativeEventCallbacks);
this.removeCreativeAsset();
this._isVPAID = false;
}
// Remove handlers from slot and videoSlot
this._removeHandlers();
// Pause video slot, and remove src
this._videoSlot.pause();
this._videoSlot.removeAttribute('src'); // empty source
this._videoSlot.load();
setTimeout(() => {
this._nextAd();
},75);
} else {
this._callEvent(this.EVENTS.AdStopped);
// abort the ad, unsubscribe and reset to a default state
this._abort();
}
}
};
AdsManager.prototype.onAdSkipped = function() {
this._callEvent(this.EVENTS.AdSkipped);
// abort the ad, unsubscribe and reset to a default state
this._abort();
};
AdsManager.prototype.onAdVolumeChange = function() {
this._callEvent(this.EVENTS.AdVolumeChange);
};
AdsManager.prototype.onAdImpression = function() {
console.log('is vpaid', this._isVPAID);
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
if (!this._hasImpression) {
// Check duration
this._attributes.duration = this._isCreativeFunctionInvokable('getAdDuration') ? this._vpaidCreative.getAdDuration() : -1;
if(this._attributes.duration !== -1) {
this._vastTracker.setDuration(this._attributes.duration);
}
/*
// Check remaining time
this._attributes.remainingTime = this._isCreativeFunctionInvokable("getAdRemainingTime") ? this._vpaidCreative.getAdRemainingTime() : -1;
console.log('update tracker with new remainingTime', this._attributes.remainingTime);
console.log('update tracker with new duration', this._attributes.duration);
*/
// Track impression
this._vastTracker.trackImpression();
// Start VPAID process
this.startVPAIDProgress();
this._hasImpression = true;
}
}
this._callEvent(this.EVENTS.AdImpression);
};
AdsManager.prototype.onAdClickThru = function(url, id, playerHandles) {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this._vastTracker.click();
}
if (this.EVENTS.AdClickThru in this._eventCallbacks) {
this._eventCallbacks[this.EVENTS.AdClickThru](url, id, playerHandles);
}
};
AdsManager.prototype.onAdVideoFirstQuartile = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this.updateVPAIDProgress();
}
this._callEvent(this.EVENTS.AdVideoFirstQuartile);
};
AdsManager.prototype.onAdVideoMidpoint = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this.updateVPAIDProgress();
}
this._callEvent(this.EVENTS.AdVideoMidpoint);
};
AdsManager.prototype.onAdVideoThirdQuartile = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this.updateVPAIDProgress();
}
this._callEvent(this.EVENTS.AdVideoThirdQuartile);
};
AdsManager.prototype.onAdPaused = function() {
if(this._vastTracker) {
this._vastTracker.setPaused(true);
}
this._callEvent(this.EVENTS.AdPaused);
};
AdsManager.prototype.onAdPlaying = function() {
if(this._vastTracker) {
this._vastTracker.setPaused(false);
}
this._callEvent(this.EVENTS.AdPlaying);
};
AdsManager.prototype.onAdVideoComplete = function() {
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
this._vastTracker.complete();
}
this._callEvent(this.EVENTS.AdVideoComplete);
};
AdsManager.prototype.onAllAdsCompleted = function() {
this._callEvent(this.EVENTS.AllAdsCompleted);
};
AdsManager.prototype.onAdError = function(message) {
this._hasError = true;
/*
// Stop and clear timeouts, intervals
this.stopVASTMediaLoadTimeout();
this.stopVPAIDProgress();
*/
this.abort();
if (this.EVENTS.AdError in this._eventCallbacks) {
this._eventCallbacks[this.EVENTS.AdError](typeof message !== 'object' ? new AdError(message) : message);
}
};
AdsManager.prototype.onAdLog = function(message) {
if (this.EVENTS.AdLog in this._eventCallbacks) {
this._eventCallbacks[this.EVENTS.AdLog](message);
}
};
AdsManager.prototype.processVASTResponse = function(res) {
const ads = res.ads;
if(ads.length != 0) {
if(ads.length > 1) {
// Ad pod
this._isAdPod = true;
console.log('ads', ads);
// Filter by sequence
this._adPod = ads.sort(function(a, b) {
const aSequence = a.sequence;
const bSequence = b.sequence;
if (aSequence === bSequence) {
return 0;
} else if (aSequence === null) {
return 1;
} else if (bSequence === null) {
return -1;
}
return (aSequence < bSequence) ? -1 : (aSequence > bSequence) ? 1 : 0;
});
// Shift from adPod array
this._ad = this._adPod.shift();
} else {
// Ad
this._ad = ads[0];
}
// Process ad
this._processAd();
} else {
// The VAST response document is empty.
this.onAdError(this.ERRORS.VAST_EMPTY_RESPONSE);
}
};
AdsManager.prototype.requestAds = function(vastUrl, options = {}) {
if(this._isDestroyed) {
return;
}
// Generate request Id
this._requestId = new Date().getTime();
// Assign options
Object.assign(this._options, options);
// VAST options
// timeout: Number - A custom timeout for the requests (default 120000 ms)
// withCredentials: Boolean - A boolean to enable the withCredentials options for the XHR URLHandler (default false)
// wrapperLimit: Number - A number of Wrapper responses that can be received with no InLine response (default 10)
// resolveAll: Boolean - Allows you to parse all the ads contained in the VAST or to parse them ad by ad or adPod by adPod (default true)
// allowMultipleAds: Boolean - A Boolean value that identifies whether multiple ads are allowed in the requested VAST response. This will override any value of allowMultipleAds attribute set in the VAST
// followAdditionalWrappers: Boolean - a Boolean value that identifies whether subsequent Wrappers after a requested VAST response is allowed. This will override any value of followAdditionalWrappers attribute set in the VAST
const vastOptions = {
timeout: this._options.vastLoadTimeout,
withCredentials: this._options.withCredentials,
wrapperLimit: this._options.wrapperLimit,
resolveAll: this._options.resolveAll,
//allowMultipleAds: true,
//followAdditionalWrappers: true,
};
// Abort
this.abort();
// Check if vastUrl exists
if(vastUrl && typeof vastUrl === 'string') {
let isURL = false;
try {
new URL(vastUrl);
isURL = true;
} catch (e) {}
if (isURL) {
// use VAST URL
this._vastClient = new VASTClient();
this._vastClient
.get(vastUrl, vastOptions)
.then(res => {
console.log('RES', res);
this.processVASTResponse(res);
})
.catch(err => {
this.onAdError(err.message);
});
} else {
// use VAST XML
const vastXml = (new window.DOMParser()).parseFromString(vastUrl, 'text/xml');
this._vastParser = new VASTParser();
this._vastParser
.parseVAST(vastXml, vastOptions)
.then(res => {
console.log('RES', res);
this.processVASTResponse(res);
})
.catch(err => {
this.onAdError(err.message);
});
}
} else {
this.onAdError('VAST URL/XML is empty');
}
};
AdsManager.prototype.canPlayVideoType = function(mimeType) {
if(mimeType === 'video/3gpp' && this.supportsThreeGPVideo()) {
return true;
} else if(mimeType === 'video/webm' && this.supportsWebmVideo()) {
return true;
} else if(mimeType === 'video/ogg' && this.supportsOggTheoraVideo()) {
return true;
} else if(mimeType === 'video/mp4' && this.supportsH264BaselineVideo()) {
return true;
}
return false;
};
AdsManager.prototype.supportsVideo = function() {
return !!document.createElement('video').canPlayType;
};
AdsManager.prototype.supportsH264BaselineVideo = function() {
if(!this.supportsVideo()) return false;
return document.createElement('video').canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
};
AdsManager.prototype.supportsOggTheoraVideo = function() {
if(!this.supportsVideo()) return false;
return document.createElement('video').canPlayType('video/ogg; codecs="theora, vorbis"');
};
AdsManager.prototype.supportsWebmVideo = function() {
if(!this.supportsVideo()) return false;
return document.createElement('video').canPlayType('video/webm; codecs="vp8, vorbis"');
};
AdsManager.prototype.supportsThreeGPVideo = function() {
if(!this.supportsVideo()) return false;
return document.createElement('video').canPlayType('video/3gpp; codecs="mp4v.20.8, samr"');
};
AdsManager.prototype.handshakeVersion = function(version) {
return this._vpaidCreative.handshakeVersion(version);
};
AdsManager.prototype._isCreativeFunctionInvokable = function(a) {
return this._vpaidCreative ? (a = this._vpaidCreative[a]) && typeof a === 'function' : false;
};
AdsManager.prototype.checkVPAIDInterface = function(a) {
const b = { passed: true, missingInterfaces: ''};
for (let d = a.length - 1; 0 <= d; d--)
this._isCreativeFunctionInvokable(a[d]) || (b.passed = false, b.missingInterfaces += a[d] + ' ');
return b;
};
AdsManager.prototype.setCallbacksForCreative = function(eventCallbacks, context) {
for (const event in eventCallbacks) eventCallbacks.hasOwnProperty(event) && this._vpaidCreative.subscribe(eventCallbacks[event], event, context)
};
AdsManager.prototype.removeCallbacksForCreative = function(eventCallbacks) {
if(this._vpaidCreative !== null) {
for (const event in eventCallbacks) {
eventCallbacks.hasOwnProperty(event) && this._vpaidCreative.unsubscribe(event); // && this._vpaidCreative.unsubscribe(eventCallbacks[event], event);
}
}
};
AdsManager.prototype.creativeAssetLoaded = function() {
const checkVPAIDMinVersion = () => {
const c = this.handshakeVersion(this.MIN_VPAID_VERSION.toFixed(1));
return c ? parseFloat(c) < this.MIN_VPAID_VERSION ? (this.onAdError('Only support creatives with VPAID version >= ' + this.MIN_VPAID_VERSION.toFixed(1)), !1) : !0 : (this.onAdError('Cannot get VPAID version from the creative'), !1)
};
if (function(that) {
const c = that.checkVPAIDInterface('handshakeVersion initAd startAd stopAd subscribe unsubscribe getAdLinear'.split(' '));
c.passed || that.onAdError('Missing interfaces in the VPAID creative: ' + c.missingInterfaces);
return c.passed
}(this) && checkVPAIDMinVersion()) {
// VPAID events
this._creativeEventCallbacks = {
AdStarted: this.onAdStarted,
AdStopped: this.onAdStopped,
AdSkipped: this.onAdSkipped,
AdLoaded: this.onAdLoaded,
//AdLinearChange: this.onAdLinearChange,
AdSizeChange: this.onAdSizeChange,
//AdExpandedChange: this.onAdExpandedChange,
AdDurationChange: this.onAdDurationChange,
AdVolumeChange: this.onAdVolumeChange,
AdImpression: this.onAdImpression,
AdClickThru: this.onAdClickThru,
//AdInteraction: this.onAdInteraction,
AdVideoStart: this.onAdVideoStart,
AdVideoFirstQuartile: this.onAdVideoFirstQuartile,
AdVideoMidpoint: this.onAdVideoMidpoint,
AdVideoThirdQuartile: this.onAdVideoThirdQuartile,
AdVideoComplete: this.onAdVideoComplete,
//AdUserAcceptInvitation: this.onAdUserAcceptInvitation,
//AdUserMinimize: this.onAdUserMinimize,
//AdUserClose: this.onAdUserClose,
AdPaused: this.onAdPaused,
AdPlaying: this.onAdPlaying, // onAdResumed
AdError: this.onAdError,
AdLog: this.onAdLog
}
// Subscribe for VPAID events
this.setCallbacksForCreative(this._creativeEventCallbacks, this);
// Prepare for iniAd
const width = this._attributes.width;
const height = this._attributes.height;
const creativeData = {
AdParameters: this._creative.adParameters
};
console.log('creativeData', creativeData);
const environmentVars = {
slot: this._slot,
videoSlot: this._videoSlot,
videoSlotCanAutoPlay: true
};
// iniAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars)
// Start loadVideoTimeout
this.startVASTMediaLoadTimeout();
try {
this._vpaidCreative.initAd(width, height, this._attributes.viewMode, this._attributes.desiredBitrate, creativeData, environmentVars);
} catch(err) {
this.onAdError(err);
}
}
};
AdsManager.prototype.handleLoadCreativeMessage = function(msg) {
if (msg && msg.data) {
const match = String(msg.data).match(new RegExp('adm:' + this._requestId + '://(.*)'));
if (match) {
console.log('vpaid creative message', match);
const value = JSON.parse(match[1]);
if(value == 'load') {
console.log('vpaid message load', this._requestId);
try {
let VPAIDAd = this._vpaidIframe.contentWindow.getVPAIDAd;
if (VPAIDAd && typeof VPAIDAd === 'function') {
VPAIDAd = VPAIDAd();
console.log('vpaid', typeof VPAIDAd);
typeof VPAIDAd === 'undefined'
? this.onAdError('getVPAIDAd() returns undefined value')
: VPAIDAd == null
? this.onAdError('getVPAIDAd() returns null')
: ((this._vpaidCreative = VPAIDAd),
this.creativeAssetLoaded());
} else {
this.onAdError('getVPAIDAd() is undefined');
}
} catch (e) {
this.onAdError(e);
}
} else if (value == 'error') {
this.onAdError('Error load VPAID');
}
}
}
};
AdsManager.prototype.loadCreativeAsset = function(fileURL) {
console.log('vpaid creative asset', fileURL);
window.addEventListener('message', this._handleLoadCreativeMessage);
// Create iframe
this._vpaidIframe = document.createElement('iframe');
this._vpaidIframe.style.display = 'none';
this._vpaidIframe.style.width = '0px';
this._vpaidIframe.style.height = '0px';
/*
// avoid document.write()
this._vpaidIframe.srcdoc = `<script>
function sendMessage(msg) {
var post = 'adm:${this._requestId}://' + JSON.stringify(msg);
window.parent.postMessage(post, '*');
}
</script>
<script type="text/javascript" onload="sendMessage('load')"
onerror="sendMessage('error')" src="${fileURL}"></script>
`;
*/
// Append iframe
this._adContainer.appendChild(this._vpaidIframe);
// Open iframe, write and close
this._vpaidIframe.contentWindow.document.open();
this._vpaidIframe.contentWindow.document.write(`
<script>function sendMessage(msg) {
var postMsg = 'adm:${this._requestId}://' + JSON.stringify(msg);
window.parent.postMessage(postMsg, '*');
} \x3c/script>
<script type="text/javascript" onload="sendMessage('load')" onerror="sendMessage('error')" src="${fileURL}"> \x3c/script>`);
this._vpaidIframe.contentWindow.document.close();
};
AdsManager.prototype.removeCreativeAsset = function() {
// Remove VPAID iframe
console.log('remove vpaid iframe');
if(this._vpaidIframe) {
this._vpaidIframe.src ='about:blank'; // unload content
this._vpaidIframe.onload = null; // remove event listeners
this._vpaidIframe.parentNode.removeChild(this._vpaidIframe); // remove from DOM
}
// Remove 3rd-party HTML elements from the own slot
console.log('remove 3rd-party HTML elements from the own slot');
[...this._ownSlot.children]
.forEach(child => {
console.log(child !== this._videoSlot);
child !== this._videoSlot && child !== this._slot ? this._ownSlot.removeChild(child) : null
});
console.log('remove 3rd-party HTML elements from the slot');
// Remove 3rd-party HTML element from the slot
[...this._slot.children]
.forEach(child => {
this._slot.removeChild(child)
});
};
AdsManager.prototype._removeHandlers = function() {
// Remove event listeners from slot
this._ownSlot.removeEventListener('click', this._handleSlotClick);
// Remove event listeners from video slot
this._videoSlot.removeEventListener('error', this._handleVideoSlotError, false);
this._videoSlot.removeEventListener('canplay', this._handleVideoSlotCanPlay);
this._videoSlot.removeEventListener('volumechange', this._handleVideoSlotVolumeChange);
this._videoSlot.removeEventListener('timeupdate', this._handleVideoSlotTimeUpdate, true);
this._videoSlot.removeEventListener('loadedmetadata', this._handleVideoSlotLoadedMetaData);
this._videoSlot.removeEventListener('ended', this._handleVideoSlotEnded);
};
AdsManager.prototype._abort = function() {
// Abort
this.abort();
// Dispatch AllAdsCompleted
this.onAllAdsCompleted();
};
AdsManager.prototype.isCreativeExists = function() {
return this._creative && this._creative.mediaFiles.length != 0;
};
AdsManager.prototype.handleSlotClick = function() {
if(!this._isVPAID && this._vastTracker) {
this._vastTracker.click();
}
};
AdsManager.prototype.handleVideoSlotError = function() {
this.onAdError(this.ERRORS.VIDEO_PLAY_ERROR);
};
AdsManager.prototype.handleVideoSlotCanPlay = function() {
console.log('video slot can play...');
};
AdsManager.prototype.handleVideoSlotVolumeChange = function(event) {
this._vastTracker && this._vastTracker.setMuted(event.target.muted);
};
AdsManager.prototype.handleVideoSlotTimeUpdate = function(event) {
if(this.isCreativeExists()) {
if (this._nextQuartileIndex >= this._quartileEvents.length) {
return;
}
const percentPlayed = (event.target.currentTime / event.target.duration) * 100; //event.target.currentTime * 100.0 / event.target.duration;
if (percentPlayed >= this._quartileEvents[this._nextQuartileIndex].value) {
const lastQuartileEvent = this._quartileEvents[this._nextQuartileIndex].event;
this._defaultEventCallbacks[lastQuartileEvent]();
this._nextQuartileIndex += 1;
}
if (percentPlayed >= 0) {
if (!this._hasImpression) {
this._vastTracker && this._vastTracker.trackImpression();
this._hasImpression = true;
}
}
console.log('percentPlayed', percentPlayed, event.target.currentTime, event.target.duration);
this._vastTracker && this._vastTracker.setProgress(event.target.currentTime);
if (event.target.duration > 0) {
this._attributes.remainingTime = event.target.duration - event.target.currentTime;
}
}
};
AdsManager.prototype.handleVideoSlotLoadedMetaData = function(event) {
this._attributes.duration = event.target.duration;
// Update tracking duration with real media meta data
this._vastTracker && this._vastTracker.setDuration(event.target.duration);
//if(!this._isVPAID) {
this.onAdDurationChange();
//}
};
AdsManager.prototype.handleVideoSlotEnded = function() {
// Complete
this._vastTracker && this._vastTracker.complete();
//setTimeout(() => {
this.onAdStopped();
//}, 75);
};
AdsManager.prototype._processAd = function(isNext = false) {
// Filter linear creatives, get first
this._creative = this._ad.creatives.filter(creative => creative.type === 'linear')[0];
// Check if creative has media files
if(this._creative) {
if(this._creative.mediaFiles.length != 0) {
// Filter and check media files for mime type canPlay and if VPAID or not
this._mediaFiles = this._creative.mediaFiles.filter(mediaFile => {
// mime types -> mp4, webm, ogg, 3gp
if(this.canPlayVideoType(mediaFile.mimeType)) {
return mediaFile;
} else if(mediaFile.mimeType === 'application/javascript') {
// apiFramework -> mime type -> application/javascript
return mediaFile;
}
});//[0]; // take the first one
// Sort media files by size
this._mediaFiles.sort(function(a, b) {
const aHeight = a.height;
const bHeight = b.height;
return (aHeight < bHeight) ? -1 : (aHeight > bHeight) ? 1 : 0;
});
if(this._mediaFiles && this._mediaFiles.length != 0) {
// Initialize VASTTracker for tracking events
this._vastTracker = new VASTTracker(null, this._ad, this._creative);
this._vastTracker.load();
if(!isNext) {
// If not VPAID dispatch AdsManagerLoaded event -> ad is ready for init
this.onAdsManagerLoaded();
} else {
this.init(this._attributes.width, this._attributes.height, this._attributes.viewMode, isNext);
}
} else {
// Linear assets were found in the VAST ad response, but none of them match the video player's capabilities.
this.onAdError(this.ERRORS.VAST_LINEAR_ASSET_MISMATCH);
}
} else {
// No assets were found in the VAST ad response.
this.onAdError(this.ERRORS.VAST_ASSET_NOT_FOUND);
}
} else {
// Non-Linear
console.log('non linear');
this.onAdError('non linear');
}
};
AdsManager.prototype._nextAd = function() {
// Shift next ad
this._ad = this._adPod.shift();
console.log('next ad', this._ad);
// Process ad
this._processAd(true);
};
AdsManager.prototype.init = function(width, height, viewMode, isNext = false) {
console.log('init....');
if(this._isDestroyed) {
return;
}
if(this.isCreativeExists()) {
if(!isNext) {
if (this._options.muted) {
this._videoSlot.muted = true;
this._videoSlot.volume = 0;
}
}
// Find the best resolution for mediaFile
this._mediaFileIndex = this._mediaFiles.findIndex(function(item) {
return item.height >= height
});
if(this._mediaFileIndex != -1) {
this._mediaFile = this._mediaFiles[this._mediaFileIndex];
} else {
// Get the last from array
this._mediaFile = this._mediaFiles[this._mediaFiles.length - 1];
}
if(this._mediaFile) {
// Check if VPAID
if (this._mediaFile.mimeType === 'application/javascript') {
this._isVPAID = true;
}
this._attributes.width = width;
this._attributes.height = height;
this._attributes.viewMode = viewMode;
// Resize slot
this.resizeSlot(this._attributes.width, this._attributes.height);
this._videoSlot.addEventListener('error', this._handleVideoSlotError, false);
if(this._isVPAID) {
// VPAID
this.loadCreativeAsset(this._mediaFile.fileURL);
} else {
// VAST
this._ownSlot.addEventListener('click', this._handleSlotClick);
// Ad video slot event listeners
this._videoSlot.addEventListener('canplay', this._handleVideoSlotCanPlay);
this._videoSlot.addEventListener('volumechange', this._handleVideoSlotVolumeChange);
this._videoSlot.addEventListener('timeupdate', this._handleVideoSlotTimeUpdate, true);
this._videoSlot.addEventListener('loadedmetadata', this._handleVideoSlotLoadedMetaData);
this._videoSlot.addEventListener('ended', this._handleVideoSlotEnded);
// Set video slot src
this._videoSlot.setAttribute('src', this._mediaFile.fileURL);
// Ad Loaded
this.onAdLoaded();
}
// Tracking
if(this._vastTracker) {
this._vastTracker.on('clickthrough', (url) => {
if (!this._isVPAID) {
this.onAdClickThru(url);
}
// Open the resolved clickThrough url
const opener = window.open(url, '_blank');
void 0 !== opener ? opener.focus() : window.location.href = url;
});
}
} else {
// console.log('media file not found');
}
} /* else {
this.onAdError('');
}*/
};
AdsManager.prototype.start = function() {
if(this.isCreativeExists()) {
// Override play function
// Backup origin play function
const _play = this._videoSlot.play;
const maxPlayRetries = 3;
let playRetries = 0;
this._videoSlot.play = function() {
//this.muted = true;
// Apply origin play, and connect to play Promise to detect autoplay exceptions
const _playPromise = _play.apply(this, [].slice.call(arguments));
if(_playPromise instanceof Promise) {
_playPromise.then(_=> {
// Autoplay worked!
console.log('autoplay worked!!!', this);
}).catch(err => {
// Autoplay failed
if(playRetries <= maxPlayRetries) {
// Try to play as muted if failed in autoplay
console.log('autoplay failed > play retries', playRetries);
this.muted = true;
this.play();
playRetries++;
}
});
}
return _playPromise;
};
if (this._isVPAID) {
this._isCreativeFunctionInvokable('startAd') && this._vpaidCreative.startAd();
} else {
//this._videoSlot.autoplay = true;
this._videoSlot.load();
this._videoSlot.play();
this.onAdStarted();
}
}
};
AdsManager.prototype.getDuration = function() {
return this.isCreativeExists() && this._attributes.duration;
};
AdsManager.prototype.pause = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
this._isCreativeFunctionInvokable('pauseAd') && this._vpaidCreative.pauseAd();
} else {
this._videoSlot.pause();
this.onAdPaused();
}
}
};
AdsManager.prototype.resume = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
this._isCreativeFunctionInvokable('resumeAd') && this._vpaidCreative.resumeAd();
} else {
this._videoSlot.play();
this.onAdPlaying();
}
}
};
AdsManager.prototype.stop = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
this._isCreativeFunctionInvokable('stopAd') && this._vpaidCreative.stopAd();
} else {
this.onAdStopped();
}
}
};
AdsManager.prototype.skip = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
this._isCreativeFunctionInvokable('skipAd') && this._vpaidCreative.skipAd();
} else {
this.onAdSkipped();
}
}
};
AdsManager.prototype.resize = function(width, height, viewMode) {
if(this.isCreativeExists()) {
this._attributes.width = width;
this._attributes.height = height;
this._attributes.viewMode = viewMode;
// Resize slot
this.resizeSlot(this._attributes.width, this._attributes.height);
if (this._isVPAID) {
this._isCreativeFunctionInvokable('resizeAd') && this._vpaidCreative.resizeAd(width, height, viewMode);
} else {
this.onAdSizeChange();
}
}
};
AdsManager.prototype.getVolume = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
return this._isCreativeFunctionInvokable('getAdVolume') ? this._vpaidCreative.getAdVolume() : -1;
}
// on iOS the video slot volume is always 1 even if the video slot is muted,
// if video slot is muted, return 0, otherwise volume
return this._videoSlot.muted ? 0 : this._videoSlot.volume;
}
};
AdsManager.prototype.setVolume = function(volume) {
if(this.isCreativeExists()) {
if (this._isVPAID) {
this._isCreativeFunctionInvokable('setAdVolume') && this._vpaidCreative.setAdVolume(volume);
}
const isVolumeChanged = volume !== this.getVolume();
this._attributes.volume = volume;
if(this._attributes.volume !== 0) {
this._videoSlot.muted = false;
} else {
this._videoSlot.muted = true;
}
this._videoSlot.volume = this._attributes.volume;
if(isVolumeChanged) {
this.onAdVolumeChange();
}
}
};
AdsManager.prototype.getRemainingTime = function() {
if(this.isCreativeExists()) {
if (this._isVPAID) {
return this._isCreativeFunctionInvokable('getAdRemainingTime') ? this._vpaidCreative.getAdRemainingTime() : -1
}
return this._attributes.remainingTime;
}
};
AdsManager.prototype.collapse = function() {
if(this.isCreativeExists()) {
if(this._isVPAID) {
this._isCreativeFunctionInvokable('collapseAd') && this._vpaidCreative.collapseAd();
}
}
};
AdsManager.prototype.expand = function() {
if(this.isCreativeExists()) {
if(this._isVPAID) {
this._isCreativeFunctionInvokable('expandAd') && this._vpaidCreative.expandAd();
}
}
};
AdsManager.prototype.abort = function() {
if(this._isDestroyed) {
return;
}
// Removes ad assets loaded at runtime that need to be properly removed at the time of ad completion
// and stops the ad and all tracking.
window.removeEventListener('message', this._handleLoadCreativeMessage);
// Stop and clear timeouts, intervals
this.stopVASTMediaLoadTimeout();
this.stopVPAIDProgress();
if(this._isVPAID) {
// Unsubscribe for VPAID events
this.removeCallbacksForCreative(this._creativeEventCallbacks);
this.removeCreativeAsset();
}
// Reset global variables to default values
this._nextQuartileIndex = 0;
this._isVPAID = false;
this._hasLoaded = false;
this._hasError = false;
this._hasImpression = false;
this._hasStarted = false;
this._ad = null;
this._adPod = null;
this._isAdPod = false;
this._creative = null;
this._mediaFile = null;
this._vpaidCreative = null;
this._vastTracker = null;
// Remove handlers from slot and videoSlot
this._removeHandlers();
// Pause video slot, and remove src
this._videoSlot.pause();
this._videoSlot.removeAttribute('src'); // empty source
this._videoSlot.load();
// Hide slot
//this.hideSlot();
this.hideVideoSlot();
};
AdsManager.prototype.destroy = function() {
if(this._isDestroyed) {
return;
}
// Cleans up the internal state, abort anything that is currently doing on with the AdsManager
// and reset to a default state.
// Reset the internal state of AdsManager
this.abort();
// Remove event listeners
this.removeEventListeners(this._eventCallbacks);
// Remove slot element from the DOM
this.removeSlot();
this._adContainer = null;
this._ownSlot = null;
this._videoSlot = null;
this._slot = null;
this._isDestroyed = true;
};
AdsManager.prototype.isDestroyed = function() {
return this._isDestroyed;
};
AdsManager.prototype.getVersion = function() {
return this._attributes.version;
};
export default AdsManager