/** * @constructor */ var 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, 'expanded' : false, 'height' : 0, 'icons' : '', 'linear' : true, 'remainingTime' : 10, 'skippableState' : false, 'viewMode' : 'normal', 'width' : 0, 'volume' : 1.0 }; /** * A set of events to be reported. * @type {Object} * @private */ this.quartileEvents_ = [ {event: 'AdVideoStart', value: 0}, {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_ = {}; /** * The current question displayed in the survey * * @type {!object} * @private */ this.currentQuestion = null; }; /** * 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 and video slot. */ 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. if (creativeData['AdParameters']) { this.parameters_ = JSON.parse(creativeData['AdParameters']); } this.updateVideoSlot_() this.videoSlot_.addEventListener( 'timeupdate', this.timeUpdateHandler_.bind(this), false); this.videoSlot_.addEventListener( 'ended', this.stopAd.bind(this), false); }; /** * Called when the overlay is clicked. * @private */ VpaidVideoPlayer.prototype.overlayOnClick_ = function() { this.callEvent_('AdClickThru'); }; /** * Called by the video element. Calls events as the video reaches times. * @private */ VpaidVideoPlayer.prototype.timeUpdateHandler_ = function() { 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; } }; /** * @private */ VpaidVideoPlayer.prototype.updateVideoSlot_ = function() { if (this.videoSlot_ == null) { this.videoSlot_ = document.createElement('video'); //this.log('Warning: No video element passed to ad, creating element.'); this.slot_.appendChild(this.videoSlot_); } this.videoSlot_.setAttribute('src', this.parameters_.videoUrl); this.updateVideoPlayerSize_(); this.callEvent_('AdImpression'); this.callEvent_('AdLoaded'); this.videoSlot_.play(); }; /** * 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'); }; function getBaseBtn(txt, url, self, btnFontSize, btnOuterRadioSize, btnInnerRadioSize, widthBtn) { var radioBtn = document.createElement("div"); radioBtn.style.width = btnOuterRadioSize; radioBtn.style.height = btnOuterRadioSize; radioBtn.style.border = "2px solid #434348"; radioBtn.style.borderRadius = "100%"; radioBtn.style.display = "flex"; radioBtn.style.justifyContent = "center"; radioBtn.style.alignItems = "center"; var radioSelected = document.createElement("div"); radioSelected.style.width = btnInnerRadioSize; radioSelected.style.height = btnInnerRadioSize; radioSelected.style.backgroundColor = "#fff"; radioSelected.style.borderRadius = "100%"; radioSelected.style.opacity = "0"; radioBtn.appendChild(radioSelected); var btn = document.createElement("button"); btn.style.width = widthBtn; btn.style.height = "15%"; btn.style.border = "none"; btn.style.color = "#fff"; btn.style.borderBottom = "1px solid #343439"; btn.style.outline = "0"; btn.style.backgroundColor = "#fff0"; btn.style.textAlign = "left"; btn.style.cursor = "pointer"; btn.style.fontSize = btnFontSize; btn.style.fontWeight = "500"; btn.style.display = "flex"; btn.style.justifyContent = "space-between"; btn.style.alignItems = "center"; btn.style.fontFamily = "'IBM Plex Sans'"; btn.style.userSelect = "none"; btn.textContent = txt; btn.appendChild(radioBtn); btn.onmouseenter = function () { this.style.backgroundColor = "#fff2"; } btn.onmouseleave = function () { this.style.backgroundColor = "#fff0"; } btn.onclick = function () { var selectedOption = self.currentQuestion.buttons.find(function(q) { return q.text == txt; }) || {}; var questions = selectedOption.questions && selectedOption.questions.length > 0 ? selectedOption.questions : self.parameters_.questions; var xml = new XMLHttpRequest(); xml.open("GET", url); xml.send(); radioSelected.style.opacity = "1"; const allBtns = btn.parentElement.querySelectorAll("button"); Array.from(allBtns).map(btn => { btn.style.backgroundColor = "#fff0"; btn.style.color = "#bbb"; btn.style.cursor = "default"; btn.setAttribute("disabled", "true"); Array.from(btn.querySelectorAll("div")).map(d => d.color = "#bbb"); }); this.style.color = "#fff"; this.style.backgroundColor = "#fff6"; Array.from(this.querySelectorAll("div")).map(d => d.color = "#fff"); if(self.currentQuestion.close_ad || selectedOption.close_ad || questions.length <= 0) { self.stopAd.bind(self)(); return btn; } var oldContainer = self.slot_.querySelector('#container-text-div'); var oldBtns = self.slot_.querySelector('#container-buttons-div'); oldContainer.style.opacity = "0"; oldBtns.style.opacity = "0"; setTimeout(function () { self.currentQuestion = questions.shift(); buildSurvey.bind(self)(self.currentQuestion); }, 400); } return btn; } function buildSurvey(question) { var textFontSize = "26px"; var lineHeightText = "32px"; var btnFontSize = "15px"; var btnOuterRadioSize = "20px"; var btnInnerRadioSize = "20px"; var widthBtn = "80%"; if (this.slot_.offsetWidth < 500) { textFontSize = '14px'; lineHeightText = '16px'; btnFontSize = "9px"; btnOuterRadioSize = "20px"; widthBtn = "90%"; btnInnerRadioSize = "10px"; } var oldContainer = this.slot_.querySelector('#container-text-div'); var oldBtns = this.slot_.querySelector('#container-buttons-div'); if (oldContainer || oldBtns) { oldContainer.remove(); oldBtns.remove(); } var containerText = document.createElement("div"); containerText.id = "container-text-div"; containerText.style.position = "absolute"; containerText.style.zIndex = "1000000"; containerText.style.width = "48%"; containerText.style.top = "0"; containerText.style.left = "0"; containerText.style.height = "100%"; containerText.style.display = "flex"; containerText.style.flexFlow = "column"; containerText.style.alignItems = "center"; containerText.style.justifyContent = "center"; containerText.style.fontSize = textFontSize; containerText.style.fontWeight = "500"; containerText.style.color = "#fff"; containerText.style.display = "flex"; containerText.style.alignItems = "center"; containerText.style.justifyContent = "center"; containerText.style.fontFamily = "'IBM Plex Sans'"; containerText.style.userSelect = "none"; containerText.style.transition = "opacity .4s"; var text = document.createElement("div"); text.textContent = question.questionText; text.style.lineHeight = lineHeightText; text.style.position = "relative"; text.style.top = "-5%"; text.style.marginLeft = "10%"; text.style.fontFamily = "'IBM Plex Sans'"; containerText.appendChild(text); var containerButtons = document.createElement("div"); containerButtons.id = "container-buttons-div"; containerButtons.style.position = "absolute"; containerButtons.style.zIndex = "1000000"; containerButtons.style.width = "55%"; containerButtons.style.top = "0"; containerButtons.style.right = "0"; containerButtons.style.height = "100%"; containerButtons.style.display = "flex"; containerButtons.style.flexFlow = "column"; containerButtons.style.alignItems = "center"; containerButtons.style.justifyContent = "center"; containerButtons.style.fontFamily = "'IBM Plex Sans'"; containerButtons.style.transition = "opacity .4s"; for (var i = 0; i < question.buttons.length; i++) { var btn = getBaseBtn(question.buttons[i].text, question.buttons[i].url, this, btnFontSize, btnOuterRadioSize, btnInnerRadioSize, widthBtn); containerButtons.appendChild(btn); } this.slot_.appendChild(containerText); this.slot_.appendChild(containerButtons); } /** * Called by the wrapper to start the ad. */ VpaidVideoPlayer.prototype.startAd = function() { var font = document.createElement("link"); font.rel = "stylesheet"; font.href = "https://fonts.googleapis.com/css?family=IBM Plex Sans"; this.slot_.appendChild(font); this.currentQuestion = this.parameters_.questions.shift(); buildSurvey.bind(this)(this.currentQuestion); 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.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_('AdResumed'); }; /** * 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, aContext) { //this.log('Subscribe ' + aCallback); var callBack = aCallback.bind(aContext); this.eventsCallbacks_[eventName] = callBack; }; /** * Removes a callback based on the eventName. * * @param {string} eventName The callback type. */ VpaidVideoPlayer.prototype.unsubscribe = function(eventName) { //this.log('unsubscribe ' + eventName); this.eventsCallbacks_[eventName] = null; }; /** * @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(message) { // console.log(message); }; /** * Calls an event if there is a callback. * @param {string} eventType * @private */ VpaidVideoPlayer.prototype.callEvent_ = function(eventType) { //console.log('?????'); //console.log(eventType); //this.log(eventType); if (eventType in this.eventsCallbacks_) { this.eventsCallbacks_[eventType](); } }; /** * 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(); };