/** * @constructor */ const VpaidVideoPlayer = function() { /** * The slot is the div element on the main page that the ad is supposed to * occupy. * @type {Object} * @private */ this.slot_ = null; /** * The video slot is the video element used by the ad to render video content. * @type {Object} * @private */ this.videoSlot_ = null; /** * An object containing all registered events. These events are all * callbacks for use by the vpaid ad. * @type {Object} * @private */ this.eventsCallbacks_ = {}; /** * A list of getable and setable attributes. * @type {Object} * @private */ this.attributes_ = { 'companions' : '', 'desiredBitrate' : 256, 'duration' : 30, 'remainingTime' : 30, 'expanded' : false, 'width' : 0, 'height' : 0, 'icons' : '', 'linear' : true, 'skippableState' : false, 'viewMode' : 'normal', 'volume' : 1.0 }; /** * A set of events to be reported. * @type {Object} * @private */ this.quartileEvents_ = [ {event: 'AdVideoFirstQuartile', value: 25}, {event: 'AdVideoMidpoint', value: 50}, {event: 'AdVideoThirdQuartile', value: 75}, {event: 'AdVideoComplete', value: 100} ]; /** * @type {number} An index into what quartile was last reported. * @private */ this.lastQuartileIndex_ = 0; /** * An array of urls and mimetype pairs. * * @type {!object} * @private */ this.parameters_ = {}; }; /** * VPAID defined init ad, initializes all attributes in the ad. The ad will * not start until startAd is called. * * @param {number} width The ad width. * @param {number} height The ad heigth. * @param {string} viewMode The ad view mode. * @param {number} desiredBitrate The desired bitrate. * @param {Object} creativeData Data associated with the creative. * @param {Object} environmentVars Variables associated with the creative like the slot, videoSlot and videoSlotCanAutoPlay. */ VpaidVideoPlayer.prototype.initAd = function(width, height, viewMode, desiredBitrate, creativeData, environmentVars) { // slot and videoSlot are passed as part of the environmentVars this.attributes_['width'] = width; this.attributes_['height'] = height; this.attributes_['viewMode'] = viewMode; this.attributes_['desiredBitrate'] = desiredBitrate; this.slot_ = environmentVars.slot; this.videoSlot_ = environmentVars.videoSlot; // Parse the incoming parameters try { this.parameters_ = JSON.parse(creativeData['AdParameters']); } catch (err) { throw new Error('Error parsing the AdParameters:', err) } this.log('initAd ' + width + 'x' + height + ' ' + viewMode + ' ' + desiredBitrate); this.updateVideoSlot_(this.parameters_.videoSrc, this.parameters_.clickThroughUrl); this.videoSlot_.addEventListener('timeupdate', this.timeUpdateHandler_.bind(this), false); this.videoSlot_.addEventListener('ended', this.stopAd.bind(this), false); // Fire pixel this.firePixelFetch(this.parameters_.pixelUrls) // Load ad this.callEvent_('AdLoaded'); // Start the ad this.callEvent_('AdVideoStart') this.callEvent_('AdImpression') }; VpaidVideoPlayer.prototype.firePixelFetch = (pixelUrls) => { if (!Array.isArray(pixelUrls) || pixelUrls.length <= 0) return pixelUrls.forEach((url) => { fetch(url, { referrer: '', referrerPolicy: 'no-referrer', mode: 'no-cors' }) }) }; // VpaidVideoPlayer.prototype.firePixelXhr = (pixelUrl) => { // const xhr = new XMLHttpRequest() // xhr.open('GET', pixelUrl) // // xhr.setRequestHeader('Origin', '*') // // xhr.setRequestHeader('Referer', '') // xhr.send() // }; // VpaidVideoPlayer.prototype.firePixelInSecureIframe = function(slot, pixelUrl) { // const iframe = document.createElement('iframe') // iframe.onload = function() { // const iDoc = iframe.contentDocument // const meta = document.createElement('meta') // meta.name = 'referrer' // meta.content = 'no-referrer' // iDoc.head.appendChild(meta) // // Pixel // const imgPixel = document.createElement('img') // imgPixel.src = pixelUrl // iDoc.body.appendChild(imgPixel) // } // slot.appendChild(iframe) // }; /** * Called when the overlay is clicked. * @private */ VpaidVideoPlayer.prototype.clickAd_ = function(url, id = 1, playerHandles = false) { // Calls the event and passes the url, id and playerHandles (if playerHandles is true player will open the url, if false VPAID should handle opening the url) this.callEvent_('AdClickThru', url, id, playerHandles); if (!playerHandles) { window.open(url, '_blank') } }; /** * Called by the video element. Calls events as the video reaches times. * @private */ VpaidVideoPlayer.prototype.timeUpdateHandler_ = function(e) { if (this.lastQuartileIndex_ >= this.quartileEvents_.length) { return; } var percentPlayed = this.videoSlot_.currentTime * 100.0 / this.videoSlot_.duration; if (percentPlayed >= this.quartileEvents_[this.lastQuartileIndex_].value) { var lastQuartileEvent = this.quartileEvents_[this.lastQuartileIndex_].event; this.eventsCallbacks_[lastQuartileEvent](); this.lastQuartileIndex_ += 1; this.log(lastQuartileEvent) } // Update remaining time this.attributes_.remainingTime = this.attributes_.duration - this.videoSlot_.currentTime this.callEvent_('AdRemainingTimeChange') }; /** * @private */ VpaidVideoPlayer.prototype.updateVideoSlot_ = function(videoSrc, clickUrl = '') { if (!this.videoSlot_) { this.videoSlot_ = document.createElement('video'); this.videoSlot_.setAttribute('muted', true) this.videoSlot_.setAttribute('autoplay', true) this.videoSlot_.setAttribute('playsinline', true) this.log('Warning: No video element passed to ad, creating element.'); this.slot_.appendChild(this.videoSlot_); } // Video this.videoSlot_.setAttribute('src', videoSrc); // Size this.updateVideoPlayerSize_(); // Styles this.videoSlot_.style.zIndex = 10 this.videoSlot_.style.cursor = 'pointer' // Click through this.videoSlot_.addEventListener('click', () => { this.clickAd_(clickUrl, 1, false) }) }; /** * Helper function to update the size of the video player. * @private */ VpaidVideoPlayer.prototype.updateVideoPlayerSize_ = function() { this.videoSlot_.setAttribute('width', this.attributes_['width']); this.videoSlot_.setAttribute('height', this.attributes_['height']); }; /** * Returns the versions of vpaid ad supported. * @param {string} version * @return {string} */ VpaidVideoPlayer.prototype.handshakeVersion = function(version) { return ('2.0'); }; /** * Called by the wrapper to start the ad. */ VpaidVideoPlayer.prototype.startAd = function() { this.log('Starting ad'); this.videoSlot_.play(); this.callEvent_('AdStarted'); }; /** * Called by the wrapper to stop the ad. */ VpaidVideoPlayer.prototype.stopAd = function() { this.log('Stopping ad'); // Calling AdStopped immediately terminates the ad. Setting a timeout allows // events to go through. var callback = this.callEvent_.bind(this); setTimeout(callback, 75, ['AdStopped']); }; /** * @param {number} value The volume in percentage. */ VpaidVideoPlayer.prototype.setAdVolume = function(value) { this.attributes_['volume'] = value; this.log('setAdVolume ' + value); this.videoSlot_.volume = value if (value > 0) { this.videoSlot_.muted = false } else { this.videoSlot_.muted = true } this.callEvent_('AdVolumeChange'); }; /** * @return {number} The volume of the ad. */ VpaidVideoPlayer.prototype.getAdVolume = function() { this.log('getAdVolume'); return this.attributes_['volume']; }; /** * @param {number} width The new width. * @param {number} height A new height. * @param {string} viewMode A new view mode. */ VpaidVideoPlayer.prototype.resizeAd = function(width, height, viewMode) { this.log('resizeAd ' + width + 'x' + height + ' ' + viewMode); this.attributes_['width'] = width; this.attributes_['height'] = height; this.attributes_['viewMode'] = viewMode; this.updateVideoPlayerSize_(); this.callEvent_('AdSizeChange'); }; /** * Pauses the ad. */ VpaidVideoPlayer.prototype.pauseAd = function() { this.log('pauseAd'); this.videoSlot_.pause(); this.callEvent_('AdPaused'); }; /** * Resumes the ad. */ VpaidVideoPlayer.prototype.resumeAd = function() { this.log('resumeAd'); this.videoSlot_.play(); this.callEvent_('AdPlaying'); }; /** * Expands the ad. */ VpaidVideoPlayer.prototype.expandAd = function() { this.log('expandAd'); this.attributes_['expanded'] = true; if (elem.requestFullscreen) { elem.requestFullscreen(); } this.callEvent_('AdExpanded'); }; /** * Returns true if the ad is expanded. * @return {boolean} */ VpaidVideoPlayer.prototype.getAdExpanded = function() { this.log('getAdExpanded'); return this.attributes_['expanded']; }; /** * Returns the skippable state of the ad. * @return {boolean} */ VpaidVideoPlayer.prototype.getAdSkippableState = function() { this.log('getAdSkippableState'); return this.attributes_['skippableState']; }; /** * Collapses the ad. */ VpaidVideoPlayer.prototype.collapseAd = function() { this.log('collapseAd'); this.attributes_['expanded'] = false; }; /** * Skips the ad. */ VpaidVideoPlayer.prototype.skipAd = function() { this.log('skipAd'); var skippableState = this.attributes_['skippableState']; if (skippableState) { this.callEvent_('AdSkipped'); } }; /** * Registers a callback for an event. * @param {Function} aCallback The callback function. * @param {string} eventName The callback type. * @param {Object} aContext The context for the callback. */ VpaidVideoPlayer.prototype.subscribe = function(aCallback, eventName) { this.log('Subscribe:', eventName); this.eventsCallbacks_[eventName] = aCallback; }; /** * Removes a callback based on the eventName. * * @param {string} eventName The callback type. */ VpaidVideoPlayer.prototype.unsubscribe = function(aCallback, eventName) { this.log('Unsubscribe:', eventName); delete this.eventsCallbacks_[eventName]; }; /** * @return {number} The ad width. */ VpaidVideoPlayer.prototype.getAdWidth = function() { return this.attributes_['width']; }; /** * @return {number} The ad height. */ VpaidVideoPlayer.prototype.getAdHeight = function() { return this.attributes_['height']; }; /** * @return {number} The time remaining in the ad. */ VpaidVideoPlayer.prototype.getAdRemainingTime = function() { return this.attributes_['remainingTime']; }; /** * @return {number} The duration of the ad. */ VpaidVideoPlayer.prototype.getAdDuration = function() { return this.attributes_['duration']; }; /** * @return {string} List of companions in vast xml. */ VpaidVideoPlayer.prototype.getAdCompanions = function() { return this.attributes_['companions']; }; /** * @return {string} A list of icons. */ VpaidVideoPlayer.prototype.getAdIcons = function() { return this.attributes_['icons']; }; /** * @return {boolean} True if the ad is a linear, false for non linear. */ VpaidVideoPlayer.prototype.getAdLinear = function() { return this.attributes_['linear']; }; /** * Logs events and messages. * * @param {string} message */ VpaidVideoPlayer.prototype.log = function() { // console.log(...arguments); }; /** * Calls an event if there is a callback. * @param {string} eventType * @private */ VpaidVideoPlayer.prototype.callEvent_ = function(eventType, ...args) { if (eventType in this.eventsCallbacks_) { this.eventsCallbacks_[eventType](...args); } }; /** * Callback for when the mute button is clicked. * @private */ VpaidVideoPlayer.prototype.muteButtonOnClick_ = function() { if (this.attributes_['volume'] == 0) { this.attributes_['volume'] = 1.0; } else { this.attributes_['volume'] = 0.0; } this.callEvent_('AdVolumeChange'); }; /** * Main function called by wrapper to get the vpaid ad. * @return {Object} The vpaid compliant ad. */ var getVPAIDAd = function() { return new VpaidVideoPlayer(); };