vast-player
Version:
Playback VAST creatives in a web browser.
1,772 lines (1,441 loc) • 157 kB
JavaScript
(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.VASTPlayer = 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){
module.exports = require('./lib/VASTPlayer');
},{"./lib/VASTPlayer":5}],2:[function(require,module,exports){
'use strict';
function proxy(event, source, target) {
source.on(event, function emit(/*...args*/) {
var args = [], length = arguments.length;
while (length--) { args[length] = arguments[length]; }
target.emit.apply(target, [event].concat(args));
});
}
function init(source, target, events) {
events.forEach(function(event) {
if (target.listeners(event).length > 0) {
proxy(event, source, target);
}
});
target.on('newListener', function handleNewListener(type) {
if (events.indexOf(type) > -1 && target.listeners(type).length < 1) {
proxy(type, source, target);
}
});
}
function EventProxy(events) {
this.events = events.slice();
this.__private__ = {
target: null,
source: null
};
}
EventProxy.prototype.from = function from(source) {
this.__private__.source = source;
if (this.__private__.target) {
init(source, this.__private__.target, this.events);
}
return this;
};
EventProxy.prototype.to = function to(target) {
this.__private__.target = target;
if (this.__private__.source) {
init(this.__private__.source, target, this.events);
}
return this;
};
module.exports = EventProxy;
},{}],3:[function(require,module,exports){
'use strict';
var VideoTracker = require('./VideoTracker');
var inherits = require('util').inherits;
var EVENTS = require('./enums/HTML_MEDIA_EVENTS');
function HTMLVideoTracker(video) {
var self = this;
VideoTracker.call(this, Math.floor(video.duration || 0)); // call super()
this.video = video;
[
EVENTS.PLAYING,
EVENTS.PAUSE,
EVENTS.TIMEUPDATE
].forEach(function(event) {
return video.addEventListener(event, function onevent() {
return self.tick();
}, false);
});
}
inherits(HTMLVideoTracker, VideoTracker);
HTMLVideoTracker.prototype._getState = function _getState() {
return {
playing: !this.video.paused,
currentTime: this.video.currentTime
};
};
module.exports = HTMLVideoTracker;
},{"./VideoTracker":7,"./enums/HTML_MEDIA_EVENTS":8,"util":35}],4:[function(require,module,exports){
'use strict';
var EVENTS = require('./enums/VPAID_EVENTS');
function identity(value) {
return value;
}
function fire(pixels, mapper) {
(pixels || []).forEach(function(src) {
new Image().src = mapper(src);
});
}
function PixelReporter(pixels, mapper) {
this.pixels = pixels.reduce(function(pixels, item) {
(pixels[item.event] || (pixels[item.event] = [])).push(item.uri);
return pixels;
}, {});
this.__private__ = {
mapper: mapper || identity
};
}
PixelReporter.prototype.track = function track(vpaid) {
var pixels = this.pixels;
var customMapper = this.__private__.mapper;
var lastVolume = vpaid.adVolume;
function fireType(type, mapper, predicate) {
function pixelMapper(url) {
return customMapper((mapper || identity)(url));
}
return function firePixels() {
if (!predicate || predicate()) {
fire(pixels[type], pixelMapper);
}
};
}
vpaid.on(EVENTS.AdSkipped, fireType('skip'));
vpaid.on(EVENTS.AdStarted, fireType('creativeView'));
vpaid.on(EVENTS.AdVolumeChange, fireType('unmute', null, function() {
return lastVolume === 0 && vpaid.adVolume > 0;
}));
vpaid.on(EVENTS.AdVolumeChange, fireType('mute', null, function() {
return lastVolume > 0 && vpaid.adVolume === 0;
}));
vpaid.on(EVENTS.AdImpression, fireType('impression'));
vpaid.on(EVENTS.AdVideoStart, fireType('start'));
vpaid.on(EVENTS.AdVideoFirstQuartile, fireType('firstQuartile'));
vpaid.on(EVENTS.AdVideoMidpoint, fireType('midpoint'));
vpaid.on(EVENTS.AdVideoThirdQuartile, fireType('thirdQuartile'));
vpaid.on(EVENTS.AdVideoComplete, fireType('complete'));
vpaid.on(EVENTS.AdClickThru, fireType('clickThrough'));
vpaid.on(EVENTS.AdUserAcceptInvitation, fireType('acceptInvitationLinear'));
vpaid.on(EVENTS.AdUserMinimize, fireType('collapse'));
vpaid.on(EVENTS.AdUserClose, fireType('closeLinear'));
vpaid.on(EVENTS.AdPaused, fireType('pause'));
vpaid.on(EVENTS.AdPlaying, fireType('resume'));
vpaid.on(EVENTS.AdError, fireType('error', function(pixel) {
return pixel.replace(/\[ERRORCODE\]/g, 901);
}));
vpaid.on(EVENTS.AdVolumeChange, function updateLastVolume() {
lastVolume = vpaid.adVolume;
});
};
module.exports = PixelReporter;
},{"./enums/VPAID_EVENTS":10}],5:[function(require,module,exports){
'use strict';
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var VAST = require('vastacular').VAST;
var JavaScriptVPAIDPlayer = require('./players/JavaScriptVPAID');
var FlashVPAIDPlayer = require('./players/FlashVPAID');
var HTMLVideoPlayer = require('./players/HTMLVideo');
var MIME = require('./enums/MIME');
var EVENTS = require('./enums/VPAID_EVENTS');
var EventProxy = require('./EventProxy');
var LiePromise = require('lie');
var PixelReporter = require('./PixelReporter');
function defaults(/*...objects*/) {
var result = {};
var length = arguments.length;
var index, object;
var prop, value;
for (index = 0; index < length; index++) {
object = arguments[index] || {};
for (prop in object) {
value = object[prop];
if (result[prop] === undefined) {
result[prop] = value;
}
if (typeof value === 'object') {
result[prop] = defaults(result[prop], value);
}
}
}
return result;
}
function identity(value) {
return value;
}
function getNotReadyError() {
return new Error('VASTPlayer not ready.');
}
function proxy(method) {
return function callMethod() {
var self = this;
var player = this.__private__.player;
if (!this.ready) {
return LiePromise.reject(getNotReadyError());
}
return player[method].apply(player, arguments).then(function() {
return self;
});
};
}
function proxyProp(property) {
return {
get: function get() {
if (!this.ready) { throw getNotReadyError(); }
return this.__private__.player[property];
},
set: function set(value) {
if (!this.ready) { throw getNotReadyError(); }
return (this.__private__.player[property] = value);
}
};
}
function VASTPlayer(container, config) {
var self = this;
EventEmitter.call(this); // call super()
this.__private__ = {
container: container,
config: defaults(config, {
vast: {
resolveWrappers: true,
maxRedirects: 5
},
tracking: {
mapper: identity
}
}),
vast: null,
ready: false,
player: null
};
this.on(EVENTS.AdClickThru, function onAdClickThru(url, id, playerHandles) {
var clickThrough = url || self.vast.get('ads[0].creatives[0].videoClicks.clickThrough');
if (playerHandles && clickThrough) {
window.open(clickThrough);
}
});
}
inherits(VASTPlayer, EventEmitter);
Object.defineProperties(VASTPlayer.prototype, {
container: {
get: function getContainer() {
return this.__private__.container;
}
},
config: {
get: function getConfig() {
return this.__private__.config;
}
},
vast: {
get: function getVast() {
return this.__private__.vast;
}
},
ready: {
get: function getReady() {
return this.__private__.ready;
}
},
adRemainingTime: proxyProp('adRemainingTime'),
adDuration: proxyProp('adDuration'),
adVolume: proxyProp('adVolume')
});
VASTPlayer.prototype.load = function load(uri) {
var self = this;
var config = this.config.vast;
return VAST.fetch(uri, config).then(function loadPlayer(vast) {
var config = (function() {
var jsVPAIDFiles = vast.filter('ads[0].creatives[0].mediaFiles', function(mediaFile) {
return (
mediaFile.type === MIME.JAVASCRIPT ||
mediaFile.type === 'application/x-javascript'
) && mediaFile.apiFramework === 'VPAID';
});
var swfVPAIDFiles = vast.filter('ads[0].creatives[0].mediaFiles', function(mediaFile) {
return mediaFile.type === MIME.FLASH && mediaFile.apiFramework === 'VPAID';
});
var files = vast.filter('ads[0].creatives[0].mediaFiles', function() { return true; });
if (jsVPAIDFiles.length > 0) {
return {
player: new JavaScriptVPAIDPlayer(self.container),
mediaFiles: jsVPAIDFiles
};
} else if (swfVPAIDFiles.length > 0) {
return {
player: new FlashVPAIDPlayer(self.container, VASTPlayer.vpaidSWFLocation),
mediaFiles: swfVPAIDFiles
};
}
return {
player: new HTMLVideoPlayer(self.container),
mediaFiles: files
};
}());
var parameters = vast.get('ads[0].creatives[0].parameters');
var pixels = [].concat(
vast.map('ads[0].impressions', function(impression) {
return { event: 'impression', uri: impression.uri };
}),
vast.map('ads[0].errors', function(uri) {
return { event: 'error', uri: uri };
}),
vast.get('ads[0].creatives[0].trackingEvents'),
vast.map('ads[0].creatives[0].videoClicks.clickTrackings', function(uri) {
return { event: 'clickThrough', uri: uri };
})
);
var player = config.player;
var mediaFiles = config.mediaFiles;
var proxy = new EventProxy(EVENTS);
var reporter = new PixelReporter(pixels, self.config.tracking.mapper);
proxy.from(player).to(self);
self.__private__.vast = vast;
self.__private__.player = player;
return player.load(mediaFiles, parameters).then(function setupPixels() {
reporter.track(player);
});
}).then(function setReady() {
self.__private__.ready = true;
self.emit('ready');
return self;
}).catch(function emitError(reason) {
self.emit('error', reason);
throw reason;
});
};
VASTPlayer.prototype.startAd = proxy('startAd');
VASTPlayer.prototype.stopAd = proxy('stopAd');
VASTPlayer.prototype.pauseAd = proxy('pauseAd');
VASTPlayer.prototype.resumeAd = proxy('resumeAd');
VASTPlayer.vpaidSWFLocation = 'https://cdn.jsdelivr.net' +
'/npm/vast-player@0.2.10/dist/vast-player--vpaid.swf';
module.exports = VASTPlayer;
},{"./EventProxy":2,"./PixelReporter":4,"./enums/MIME":9,"./enums/VPAID_EVENTS":10,"./players/FlashVPAID":12,"./players/HTMLVideo":13,"./players/JavaScriptVPAID":14,"events":19,"lie":21,"util":35,"vastacular":36}],6:[function(require,module,exports){
'use strict';
function VPAIDVersion(versionString) {
var parts = versionString.split('.').map(parseFloat);
this.string = versionString;
this.major = parts[0];
this.minor = parts[1];
this.patch = parts[2];
Object.freeze(this);
}
VPAIDVersion.prototype.toString = function toString() {
return this.string;
};
module.exports = VPAIDVersion;
},{}],7:[function(require,module,exports){
'use strict';
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var EVENTS = require('./enums/VPAID_EVENTS');
function fire(event, tracker) {
if (tracker.fired[event]) { return; }
tracker.emit(event);
tracker.fired[event] = true;
}
function VideoTracker(duration) {
EventEmitter.apply(this, arguments); // call super()
this.duration = duration;
this.seconds = Array.apply([], new Array(duration)).map(function() { return false; });
this.fired = [
EVENTS.AdVideoStart,
EVENTS.AdVideoFirstQuartile,
EVENTS.AdVideoMidpoint,
EVENTS.AdVideoThirdQuartile,
EVENTS.AdVideoComplete
].reduce(function(fired, event) {
fired[event] = false;
return fired;
}, {});
}
inherits(VideoTracker, EventEmitter);
VideoTracker.prototype.tick = function tick() {
var seconds = this.seconds;
var state = this._getState();
var index = Math.round(state.currentTime) - 1;
var quartileIndices = [1, 2, 3, 4].map(function(quartile) {
return Math.floor(this.duration / 4 * quartile);
}, this);
function quartileViewed(quartile) {
var end = quartileIndices[quartile - 1];
return seconds.slice(0, end).every(function(second) {
return second === true;
});
}
if (state.playing) {
fire(EVENTS.AdVideoStart, this);
if (index > -1) {
this.seconds[index] = true;
}
}
if (quartileViewed(1)) {
fire(EVENTS.AdVideoFirstQuartile, this);
}
if (quartileViewed(2)) {
fire(EVENTS.AdVideoMidpoint, this);
}
if (quartileViewed(3)) {
fire(EVENTS.AdVideoThirdQuartile, this);
}
if (quartileViewed(4)) {
fire(EVENTS.AdVideoComplete, this);
}
};
module.exports = VideoTracker;
},{"./enums/VPAID_EVENTS":10,"events":19,"util":35}],8:[function(require,module,exports){
'use strict';
var HTML_MEDIA_EVENTS = [
'abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'encrypted',
'ended',
'error',
'interruptbegin',
'interruptend',
'loadeddata',
'loadedmetadata',
'loadstart',
'mozaudioavailable',
'pause',
'play',
'playing',
'progress',
'ratechange',
'seeked',
'seeking',
'stalled',
'suspend',
'timeupdate',
'volumechange',
'waiting'
];
HTML_MEDIA_EVENTS.forEach(function(event) {
this[event.toUpperCase()] = event;
}, HTML_MEDIA_EVENTS);
Object.freeze(HTML_MEDIA_EVENTS);
module.exports = HTML_MEDIA_EVENTS;
},{}],9:[function(require,module,exports){
var MIME = {
JAVASCRIPT: 'application/javascript',
FLASH: 'application/x-shockwave-flash'
};
Object.freeze(MIME);
module.exports = MIME;
},{}],10:[function(require,module,exports){
'use strict';
var VPAID_EVENTS = [
'AdLoaded',
'AdStarted',
'AdStopped',
'AdSkipped',
'AdSkippableStateChange',
'AdSizeChange',
'AdLinearChange',
'AdDurationChange',
'AdExpandedChange',
'AdRemainingTimeChange',
'AdVolumeChange',
'AdImpression',
'AdVideoStart',
'AdVideoFirstQuartile',
'AdVideoMidpoint',
'AdVideoThirdQuartile',
'AdVideoComplete',
'AdClickThru',
'AdInteraction',
'AdUserAcceptInvitation',
'AdUserMinimize',
'AdUserClose',
'AdPaused',
'AdPlaying',
'AdLog',
'AdError'
];
VPAID_EVENTS.forEach(function(event) {
VPAID_EVENTS[event] = event;
});
Object.freeze(VPAID_EVENTS);
module.exports = VPAID_EVENTS;
},{}],11:[function(require,module,exports){
'use strict';
var win = require('./window');
var video = document.createElement('video');
var MIME = require('./enums/MIME');
exports.isDesktop = !/Android|Silk|Mobile|PlayBook/.test(win.navigator.userAgent);
exports.canPlay = function canPlay(type) {
var mimeTypes = win.navigator.mimeTypes;
var ActiveXObject = win.ActiveXObject;
switch (type) {
case MIME.FLASH:
try {
return new ActiveXObject('ShockwaveFlash.ShockwaveFlash') ? 2 : 0;
} catch (e) {
return !!(mimeTypes && mimeTypes[MIME.FLASH]) ? 2 : 0;
}
return 0;
case MIME.JAVASCRIPT:
case 'application/x-javascript':
return 2;
default:
if (video.canPlayType) {
switch (video.canPlayType(type)) {
case 'probably':
return 2;
case 'maybe':
return 1;
default:
return 0;
}
}
}
return 0;
};
Object.freeze(exports);
},{"./enums/MIME":9,"./window":17}],12:[function(require,module,exports){
'use strict';
var VPAID = require('./VPAID');
var inherits = require('util').inherits;
var LiePromise = require('lie');
var uuid = require('../uuid');
var querystring = require('querystring');
var EVENTS = require('../enums/VPAID_EVENTS');
var VPAIDVersion = require('../VPAIDVersion');
function FlashVPAID(container, swfURI) {
VPAID.apply(this, arguments); // call super()
this.swfURI = swfURI;
this.object = null;
}
inherits(FlashVPAID, VPAID);
FlashVPAID.prototype.load = function load(mediaFiles, parameters) {
var self = this;
var uri = mediaFiles[0].uri;
var bitrate = mediaFiles[0].bitrate;
return new LiePromise(function loadCreative(resolve, reject) {
var vpaid = document.createElement('object');
var eventFnName = 'vast_player__' + uuid(20);
var flashvars = querystring.stringify({
vpaidURI: uri,
eventCallback: eventFnName
});
function cleanup(reason) {
self.container.removeChild(vpaid);
self.api = null;
self.object = null;
delete window[eventFnName];
if (reason) {
reject(reason);
}
}
vpaid.type = 'application/x-shockwave-flash';
vpaid.data = self.swfURI + '?' + flashvars;
vpaid.style.display = 'block';
vpaid.style.width = '100%';
vpaid.style.height = '100%';
vpaid.style.border = 'none';
vpaid.style.opacity = '0';
vpaid.innerHTML = [
'<param name="movie" value="' + self.swfURI + '">',
'<param name="flashvars" value="' + flashvars + '">',
'<param name="quality" value="high">',
'<param name="play" value="false">',
'<param name="loop" value="false">',
'<param name="wmode" value="opaque">',
'<param name="scale" value="noscale">',
'<param name="salign" value="lt">',
'<param name="allowScriptAccess" value="always">'
].join('\n');
self.object = vpaid;
window[eventFnName] = function handleVPAIDEvent(event) {
switch (event.type) {
case EVENTS.AdClickThru:
return self.emit(event.type, event.url, event.Id, event.playerHandles);
case EVENTS.AdInteraction:
case EVENTS.AdLog:
return self.emit(event.type, event.Id);
case EVENTS.AdError:
return self.emit(event.type, event.message);
default:
return self.emit(event.type);
}
};
self.once('VPAIDInterfaceReady', function initAd() {
var position = vpaid.getBoundingClientRect();
var version = self.vpaidVersion = new VPAIDVersion(vpaid.handshakeVersion('2.0'));
if (version.major > 2) {
return reject(new Error('VPAID version ' + version + ' is not supported.'));
}
self.on('VPAIDInterfaceResize', function resizeAd() {
var position = vpaid.getBoundingClientRect();
self.resizeAd(position.width, position.height, 'normal');
});
vpaid.initAd(position.width, position.height, 'normal', bitrate, parameters, null);
});
self.once(EVENTS.AdLoaded, function handleAdLoaded() {
self.api = vpaid;
vpaid.style.opacity = '1';
resolve(self);
});
self.once(EVENTS.AdError, function handleAdError(reason) {
cleanup(new Error(reason));
});
self.once(EVENTS.AdStopped, cleanup);
self.container.appendChild(vpaid);
});
};
module.exports = FlashVPAID;
},{"../VPAIDVersion":6,"../enums/VPAID_EVENTS":10,"../uuid":16,"./VPAID":15,"lie":21,"querystring":26,"util":35}],13:[function(require,module,exports){
'use strict';
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var LiePromise = require('lie');
var canPlay = require('../environment').canPlay;
var sortBy = require('sort-by');
var VPAID_EVENTS = require('../enums/VPAID_EVENTS');
var HTML_MEDIA_EVENTS = require('../enums/HTML_MEDIA_EVENTS');
var HTMLVideoTracker = require('../HTMLVideoTracker');
var EventProxy = require('../EventProxy');
function on(video, event, handler) {
return video.addEventListener(event, handler, false);
}
function off(video, event, handler) {
return video.removeEventListener(event, handler, false);
}
function once(video, event, handler) {
return on(video, event, function onevent() {
off(video, event, onevent);
return handler.apply(this, arguments);
});
}
function method(implementation, promiseify) {
function getError() {
return new Error('The <video> has not been loaded.');
}
return function callImplementation(/*...args*/) {
if (!this.video) {
if (promiseify) { return LiePromise.reject(getError()); } else { throw getError(); }
}
return implementation.apply(this, arguments);
};
}
function pickMediaFile(mediaFiles, dimensions) {
var width = dimensions.width;
var items = mediaFiles.map(function(mediaFile) {
return {
mediaFile: mediaFile,
playability: canPlay(mediaFile.type)
};
}).filter(function(config) {
return config.playability > 0;
}).sort(sortBy('-playability', '-mediaFile.bitrate'));
var distances = items.map(function(item) {
return Math.abs(width - item.mediaFile.width);
});
var item = items[distances.indexOf(Math.min.apply(Math, distances))];
return (!item || item.playability < 1) ? null : item.mediaFile;
}
function HTMLVideo(container) {
this.container = container;
this.video = null;
this.__private__ = {
hasPlayed: false
};
}
inherits(HTMLVideo, EventEmitter);
Object.defineProperties(HTMLVideo.prototype, {
adRemainingTime: { get: method(function getAdRemainingTime() {
return this.video.duration - this.video.currentTime;
}) },
adDuration: { get: method(function getAdDuration() { return this.video.duration; }) },
adVolume: {
get: method(function getAdVolume() { return this.video.volume; }),
set: method(function setAdVolume(volume) { this.video.volume = volume; })
}
});
HTMLVideo.prototype.load = function load(mediaFiles) {
var self = this;
return new LiePromise(function loadCreative(resolve, reject) {
var video = document.createElement('video');
var mediaFile = pickMediaFile(mediaFiles, self.container.getBoundingClientRect());
if (!mediaFile) {
return reject(new Error('There are no playable <MediaFile>s.'));
}
video.setAttribute('webkit-playsinline', true);
video.src = mediaFile.uri;
video.preload = 'auto';
video.style.display = 'block';
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'contain';
once(video, HTML_MEDIA_EVENTS.LOADEDMETADATA, function onloadedmetadata() {
var tracker = new HTMLVideoTracker(video);
var proxy = new EventProxy(VPAID_EVENTS);
proxy.from(tracker).to(self);
self.video = video;
resolve(self);
self.emit(VPAID_EVENTS.AdLoaded);
on(video, HTML_MEDIA_EVENTS.DURATIONCHANGE, function ondurationchange() {
self.emit(VPAID_EVENTS.AdDurationChange);
});
on(video, HTML_MEDIA_EVENTS.VOLUMECHANGE, function onvolumechange() {
self.emit(VPAID_EVENTS.AdVolumeChange);
});
});
once(video, HTML_MEDIA_EVENTS.ERROR, function onerror() {
var error = video.error;
self.emit(VPAID_EVENTS.AdError, error.message);
reject(error);
});
once(video, HTML_MEDIA_EVENTS.PLAYING, function onplaying() {
self.__private__.hasPlayed = true;
self.emit(VPAID_EVENTS.AdImpression);
});
once(video, HTML_MEDIA_EVENTS.ENDED, function onended() {
self.stopAd();
});
on(video, 'click', function onclick() {
self.emit(VPAID_EVENTS.AdClickThru, null, null, true);
});
self.container.appendChild(video);
});
};
HTMLVideo.prototype.startAd = method(function startAd() {
var self = this;
var video = this.video;
if (this.__private__.hasPlayed) {
return LiePromise.reject(new Error('The ad has already been started.'));
}
return new LiePromise(function callPlay(resolve) {
once(video, HTML_MEDIA_EVENTS.PLAYING, function onplaying() {
resolve(self);
self.emit(VPAID_EVENTS.AdStarted);
});
return video.play();
});
}, true);
HTMLVideo.prototype.stopAd = method(function stopAd() {
this.container.removeChild(this.video);
this.emit(VPAID_EVENTS.AdStopped);
return LiePromise.resolve(this);
}, true);
HTMLVideo.prototype.pauseAd = method(function pauseAd() {
var self = this;
var video = this.video;
if (this.video.paused) {
return LiePromise.resolve(this);
}
return new LiePromise(function callPause(resolve) {
once(video, HTML_MEDIA_EVENTS.PAUSE, function onpause() {
resolve(self);
self.emit(VPAID_EVENTS.AdPaused);
});
return video.pause();
});
}, true);
HTMLVideo.prototype.resumeAd = method(function resumeAd() {
var self = this;
var video = this.video;
if (!this.__private__.hasPlayed) {
return LiePromise.reject(new Error('The ad has not been started yet.'));
}
if (!this.video.paused) {
return LiePromise.resolve(this);
}
return new LiePromise(function callPlay(resolve) {
once(video, HTML_MEDIA_EVENTS.PLAY, function onplay() {
resolve(self);
self.emit(VPAID_EVENTS.AdPlaying);
});
return video.play();
});
}, true);
module.exports = HTMLVideo;
},{"../EventProxy":2,"../HTMLVideoTracker":3,"../enums/HTML_MEDIA_EVENTS":8,"../enums/VPAID_EVENTS":10,"../environment":11,"events":19,"lie":21,"sort-by":28,"util":35}],14:[function(require,module,exports){
'use strict';
var inherits = require('util').inherits;
var VPAID = require('./VPAID');
var LiePromise = require('lie');
var EVENTS = require('../enums/VPAID_EVENTS');
var isDesktop = require('../environment').isDesktop;
var VPAIDVersion = require('../VPAIDVersion');
function JavaScriptVPAID() {
VPAID.apply(this, arguments); // call super()
this.frame = null;
}
inherits(JavaScriptVPAID, VPAID);
JavaScriptVPAID.prototype.load = function load(mediaFiles, parameters) {
var self = this;
var uri = mediaFiles[0].uri;
var bitrate = mediaFiles[0].bitrate;
return new LiePromise(function loadCreative(resolve, reject) {
var iframe = document.createElement('iframe');
var script = document.createElement('script');
var video = document.createElement('video');
function cleanup(reason) {
self.container.removeChild(iframe);
self.frame = null;
self.api = null;
if (reason) {
reject(reason);
}
}
iframe.src = 'about:blank';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.display = 'block';
iframe.style.opacity = '0';
iframe.style.border = 'none';
video.setAttribute('webkit-playsinline', 'true');
video.style.display = 'block';
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'contain';
self.container.appendChild(iframe);
// Opening the iframe document for writing causes it to inherit its parent's location
iframe.contentWindow.document.open();
iframe.contentWindow.document.close();
iframe.contentWindow.document.body.style.margin = '0';
self.frame = iframe;
script.src = uri;
script.onload = function onload() {
var vpaid = iframe.contentWindow.getVPAIDAd();
var position = iframe.getBoundingClientRect();
var slot = iframe.contentWindow.document.body;
var version = self.vpaidVersion = new VPAIDVersion(vpaid.handshakeVersion('2.0'));
function resizeAd() {
var position = iframe.getBoundingClientRect();
self.resizeAd(position.width, position.height, 'normal');
}
if (version.major > 2) {
return reject(new Error('VPAID version ' + version + ' is not supported.'));
}
iframe.contentWindow.addEventListener('resize', resizeAd, false);
EVENTS.forEach(function subscribe(event) {
return vpaid.subscribe(function handle(/*...args*/) {
var args = new Array(arguments.length);
var length = arguments.length;
while (length--) { args[length] = arguments[length]; }
return self.emit.apply(self, [event].concat(args));
}, event);
});
self.once(EVENTS.AdLoaded, function onAdLoaded() {
iframe.style.opacity = '1';
self.api = vpaid;
resolve(self);
});
self.once(EVENTS.AdError, function onAdError(reason) {
cleanup(new Error(reason));
});
self.once(EVENTS.AdStopped, cleanup);
vpaid.initAd(
position.width,
position.height,
'normal',
bitrate,
{ AdParameters: parameters },
{ slot: slot, videoSlot: video, videoSlotCanAutoPlay: isDesktop }
);
};
script.onerror = function onerror() {
cleanup(new Error('Failed to load MediaFile [' + uri + '].'));
};
iframe.contentWindow.document.body.appendChild(video);
iframe.contentWindow.document.head.appendChild(script);
});
};
module.exports = JavaScriptVPAID;
},{"../VPAIDVersion":6,"../enums/VPAID_EVENTS":10,"../environment":11,"./VPAID":15,"lie":21,"util":35}],15:[function(require,module,exports){
'use strict';
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var LiePromise = require('lie');
var EVENTS = require('../enums/VPAID_EVENTS');
function proxy(method, event) {
return function callMethod(/*..args*/) {
var args = arguments;
var api = this.api;
var self = this;
function getError() {
return new Error('Ad has not been loaded.');
}
function call() {
return api[method].apply(api, args);
}
if (!event) {
if (!api) {
throw getError();
}
return call();
}
return new LiePromise(function(resolve, reject) {
if (!api) {
return reject(getError());
}
self.once(event, function done() {
resolve(self);
});
return call();
});
};
}
function VPAID(container) {
this.container = container;
this.api = null;
this.vpaidVersion = null;
}
inherits(VPAID, EventEmitter);
Object.defineProperties(VPAID.prototype, {
adLinear: { get: proxy('getAdLinear') },
adWidth: { get: proxy('getAdWidth') },
adHeight: { get: proxy('getAdHeight') },
adExpanded: { get: proxy('getAdExpanded') },
adSkippableState: { get: proxy('getAdSkippableState') },
adRemainingTime: { get: proxy('getAdRemainingTime') },
adDuration: { get: proxy('getAdDuration') },
adVolume: { get: proxy('getAdVolume'), set: proxy('setAdVolume') },
adCompanions: { get: proxy('getAdCompanions') },
adIcons: { get: proxy('getAdIcons') }
});
VPAID.prototype.load = function load() {
throw new Error('VPAID subclass must implement load() method.');
};
VPAID.prototype.resizeAd = proxy('resizeAd', EVENTS.AdSizeChange);
VPAID.prototype.startAd = proxy('startAd', EVENTS.AdStarted);
VPAID.prototype.stopAd = proxy('stopAd', EVENTS.AdStopped);
VPAID.prototype.pauseAd = proxy('pauseAd', EVENTS.AdPaused);
VPAID.prototype.resumeAd = proxy('resumeAd', EVENTS.AdPlaying);
VPAID.prototype.expandAd = proxy('expandAd', EVENTS.AdExpandedChange);
VPAID.prototype.collapseAd = proxy('collapseAd', EVENTS.AdExpandedChange);
VPAID.prototype.skipAd = proxy('skipAd', EVENTS.AdSkipped);
module.exports = VPAID;
},{"../enums/VPAID_EVENTS":10,"events":19,"lie":21,"util":35}],16:[function(require,module,exports){
'use strict';
var POSSIBILITIES = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var POSSIBILITIES_LENGTH = POSSIBILITIES.length;
module.exports = function uuid(length) {
var result = '';
while (length--) {
result += POSSIBILITIES.charAt(Math.floor(Math.random() * POSSIBILITIES_LENGTH));
}
return result;
};
},{}],17:[function(require,module,exports){
module.exports = window;
},{}],18:[function(require,module,exports){
/**
* Expose `Emitter`.
*/
if (typeof module !== 'undefined') {
module.exports = Emitter;
}
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
};
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks['$' + event] = this._callbacks['$' + event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
function on() {
this.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks['$' + event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks['$' + event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = [].slice.call(arguments, 1)
, callbacks = this._callbacks['$' + event];
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks['$' + event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
},{}],19:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
function EventEmitter() {
this._events = this._events || {};
this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
if (!isNumber(n) || n < 0 || isNaN(n))
throw TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
EventEmitter.prototype.emit = function(type) {
var er, handler, len, args, i, listeners;
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events.error ||
(isObject(this._events.error) && !this._events.error.length)) {
er = arguments[1];
if (er instanceof Error) {
throw er; // Unhandled 'error' event
}
throw TypeError('Uncaught, unspecified "error" event.');
}
}
handler = this._events[type];
if (isUndefined(handler))
return false;
if (isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
handler.apply(this, args);
}
} else if (isObject(handler)) {
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++)
listeners[i].apply(this, args);
}
return true;
};
EventEmitter.prototype.addListener = function(type, listener) {
var m;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events)
this._events = {};
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (this._events.newListener)
this.emit('newListener', type,
isFunction(listener.listener) ?
listener.listener : listener);
if (!this._events[type])
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
else if (isObject(this._events[type]))
// If we've already got an array, just append.
this._events[type].push(listener);
else
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
// Check for listener leak
if (isObject(this._events[type]) && !this._events[type].warned) {
var m;
if (!isUndefined(this._maxListeners)) {
m = this._maxListeners;
} else {
m = EventEmitter.defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
if (typeof console.trace === 'function') {
// not supported in IE 10
console.trace();
}
}
}
return this;
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.once = function(type, listener) {
if (!isFunction(listener))
throw TypeError('listener must be a function');
var fired = false;
function g() {
this.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
g.listener = listener;
this.on(type, g);
return this;
};
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
var list, position, length, i;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events || !this._events[type])
return this;
list = this._events[type];
length = list.length;
position = -1;
if (list === listener ||
(isFunction(list.listener) && list.listener === listener)) {
delete this._events[type];
if (this._events.removeListener)
this.emit('removeListener', type, listener);
} else if (isObject(list)) {
for (i = length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list.length = 0;
delete this._events[type];
} else {
list.splice(position, 1);
}
if (this._events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function(type) {
var key, listeners;
if (!this._events)
return this;
// not listening for removeListener, no need to emit
if (!this._events.removeListener) {
if (arguments.length === 0)
this._events = {};
else if (this._events[type])
delete this._events[type];
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (key in this._events) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = {};
return this;
}
listeners = this._events[type];
if (isFunction(listeners)) {
this.removeListener(type, listeners);
} else {
// LIFO order
while (listeners.length)
this.removeListener(type, listeners[listeners.length - 1]);
}
delete this._events[type];
return this;
};
EventEmitter.prototype.listeners = function(type) {
var ret;
if (!this._events || !this._events[type])
ret = [];
else if (isFunction(this._events[type]))
ret = [this._events[type]];
else
ret = this._events[type].slice();
return ret;
};
EventEmitter.listenerCount = function(emitter, type) {
var ret;
if (!emitter._events || !emitter._events[type])
ret = 0;
else if (isFunction(emitter._events[type]))
ret = 1;
else
ret = emitter._events[type].length;
return ret;
};
function isFunction(arg) {
return typeof arg === 'function';
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isUndefined(arg) {
return arg === void 0;
}
},{}],20:[function(require,module,exports){
(function (global){
'use strict';
var Mutation = global.MutationObserver || global.WebKitMutationObserver;
var scheduleDrain;
{
if (Mutation) {
var called = 0;
var observer = new Mutation(nextTick);
var element = global.document.createTextNode('');
observer.observe(element, {
characterData: true
});
scheduleDrain = function () {
element.data = (called = ++called % 2);
};
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
var channel = new global.MessageChannel();
channel.port1.onmessage = nextTick;
scheduleDrain = function () {
channel.port2.postMessage(0);
};
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
scheduleDrain = function () {
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement('script');
scriptEl.onreadystatechange = function () {
nextTick();
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
};
} else {
scheduleDrain = function () {
setTimeout(nextTick, 0);
};
}
}
var draining;
var queue = [];
//named nextTick for less confusing stack traces
function nextTick() {
draining = true;
var i, oldQueue;
var len = queue.length;
while (len) {
oldQueue = queue;
queue = [];
i = -1;
while (++i < len) {
oldQueue[i]();
}
len = queue.length;
}
draining = false;
}
module.exports = immediate;
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],21:[function(require,module,exports){
'use strict';
var immediate = require('immediate');
/* istanbul ignore next */
function INTERNAL() {}
var handlers = {};
var REJECTED = ['REJECTED'];
var FULFILLED = ['FULFILLED'];
var PENDING = ['PENDING'];
module.exports = Promise;
function Promise(resolver) {
if (typeof resolver !== 'function') {
throw new TypeError('resolver must be a function');
}
this.state = PENDING;
this.queue = [];
this.outcome = void 0;
if (resolver !== INTERNAL) {
safelyResolveThenable(this, resolver);
}
}
Promise.prototype["catch"] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.then = function (onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
typeof onRejected !== 'function' && this.state === REJECTED) {
return this;
}
var promise = new this.constructor(INTERNAL);
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
unwrap(promise, resolver, this.outcome);
} else {
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}
return promise;
};
function QueueItem(promise, onFulfilled, onRejected) {
this.promise = promise;
if (typeof onFulfilled === 'function') {
this.onFulfilled = onFulfilled;
this.callFulfilled = this.otherCallFulfilled;
}
if (typeof onRejected === 'function') {
this.onRejected = onRejected;
this.callRejected = this.otherCallRejected;
}
}
QueueItem.prototype.callFulfilled = function (value) {
handlers.resolve(this.promise, value);
};
QueueItem.prototype.otherCallFulfilled = function (value) {
unwrap(this.promise, this