From 4118ab1ad1a6e3afd4578005ac29e1a2a662b58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Tue, 26 Jul 2016 17:22:23 -0400 Subject: [PATCH 1/7] Added support to trackingEvents in VAST files. Generally it fires start, firstQuartile, middle, thirdQuartile and complete events. --- videojs.vast.js | 651 +++++++++++++++++++++++++----------------------- 1 file changed, 342 insertions(+), 309 deletions(-) diff --git a/videojs.vast.js b/videojs.vast.js index 429847e..4521900 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -1,326 +1,359 @@ -(function(window, vjs, vast) { - 'use strict'; - - var extend = function(obj) { - var arg, i, k; - for (i = 1; i < arguments.length; i++) { - arg = arguments[i]; - for (k in arg) { - if (arg.hasOwnProperty(k)) { - obj[k] = arg[k]; - } - } - } - return obj; - }, - - defaults = { - // seconds before skip button shows, negative values to disable skip button altogether - skip: 5 - }, - - Vast = function (player, settings) { - - // return vast plugin - return { - createSourceObjects: function (media_files) { - var sourcesByFormat = {}, i, j, tech; - var techOrder = player.options().techOrder; - for (i = 0, j = techOrder.length; i < j; i++) { - var techName = techOrder[i].charAt(0).toUpperCase() + techOrder[i].slice(1); - tech = window.videojs[techName]; - - // Check if the current tech is defined before continuing - if (!tech) { - continue; - } - - // Check if the browser supports this technology - if (tech.isSupported()) { - // Loop through each source object - for (var a = 0, b = media_files.length; a < b; a++) { - var media_file = media_files[a]; - var source = {type:media_file.mimeType, src:media_file.fileURL}; - // Check if source can be played with this technology - if (tech.canPlaySource(source)) { - if (sourcesByFormat[techOrder[i]] === undefined) { - sourcesByFormat[techOrder[i]] = []; +(function (window, vjs, vast) { + 'use strict'; + var extend = function (obj) { + var arg, i, k; + for (i = 1; i < arguments.length; i++) { + arg = arguments[i]; + for (k in arg) { + if (arg.hasOwnProperty(k)) { + obj[k] = arg[k]; + } } - sourcesByFormat[techOrder[i]].push({ - type:media_file.mimeType, - src: media_file.fileURL, - width: media_file.width, - height: media_file.height - }); - } - } - } - } - // Create sources in preferred format order - var sources = []; - for (j = 0; j < techOrder.length; j++) { - tech = techOrder[j]; - if (sourcesByFormat[tech] !== undefined) { - for (i = 0; i < sourcesByFormat[tech].length; i++) { - sources.push(sourcesByFormat[tech][i]); } - } - } - return sources; - }, - - getContent: function () { - - // query vast url given in settings - vast.client.get(settings.url, function(response) { - if (response) { - // we got a response, deal with it - for (var adIdx = 0; adIdx < response.ads.length; adIdx++) { - var ad = response.ads[adIdx]; - player.vast.companion = undefined; - for (var creaIdx = 0; creaIdx < ad.creatives.length; creaIdx++) { - var creative = ad.creatives[creaIdx], foundCreative = false, foundCompanion = false; - if (creative.type === "linear" && !foundCreative) { - - if (creative.mediaFiles.length) { - - player.vast.sources = player.vast.createSourceObjects(creative.mediaFiles); - - if (!player.vast.sources.length) { - player.trigger('adscanceled'); - return; + return obj; + }, + + defaults = { + // seconds before skip button shows, negative values to disable skip button altogether + skip: 5 + }, + + Vast = function (player, settings) { + + // return vast plugin + return { + createSourceObjects: function (media_files) { + var sourcesByFormat = {}, i, j, tech; + var techOrder = player.options().techOrder; + for (i = 0, j = techOrder.length; i < j; i++) { + var techName = techOrder[i].charAt(0).toUpperCase() + techOrder[i].slice(1); + tech = window.videojs[techName]; + + // Check if the current tech is defined before continuing + if (!tech) { + continue; + } + + // Check if the browser supports this technology + if (tech.isSupported()) { + // Loop through each source object + for (var a = 0, b = media_files.length; a < b; a++) { + var media_file = media_files[a]; + var source = {type: media_file.mimeType, src: media_file.fileURL}; + // Check if source can be played with this technology + if (tech.canPlaySource(source)) { + if (sourcesByFormat[techOrder[i]] === undefined) { + sourcesByFormat[techOrder[i]] = []; + } + sourcesByFormat[techOrder[i]].push({ + type: media_file.mimeType, + src: media_file.fileURL, + width: media_file.width, + height: media_file.height + }); + } + } + } + } + // Create sources in preferred format order + var sources = []; + for (j = 0; j < techOrder.length; j++) { + tech = techOrder[j]; + if (sourcesByFormat[tech] !== undefined) { + for (i = 0; i < sourcesByFormat[tech].length; i++) { + sources.push(sourcesByFormat[tech][i]); + } + } + } + return sources; + }, + callTriggerLink(links){ + var request = new XMLHttpRequest(); + links.forEach(function (link) { + request.open("GET", link); + request.send(); + }); + }, + getContent: function () { + // query vast url given in settings + vast.client.get(settings.url, function (response) { + if (response) { + // we got a response, deal with it + for (var adIdx = 0; adIdx < response.ads.length; adIdx++) { + var ad = response.ads[adIdx]; + player.vast.companion = undefined; + for (var creaIdx = 0; creaIdx < ad.creatives.length; creaIdx++) { + var creative = ad.creatives[creaIdx], foundCreative = false, foundCompanion = false; + if (creative.type === "linear" && !foundCreative) { + + if (creative.mediaFiles.length) { + + player.vast.sources = player.vast.createSourceObjects(creative.mediaFiles); + + if (!player.vast.sources.length) { + player.trigger('adscanceled'); + return; + } + + player.vastTracker = new vast.tracker(ad, creative); + foundCreative = true; + } + + } else if (creative.type === "companion" && !foundCompanion) { + + player.vast.companion = creative; + + foundCompanion = true; + + } + } + + if (player.vastTracker) { + player.vast.setupVideoDurationWatcher(); + // vast tracker and content is ready to go, trigger event + player.trigger('vast-ready'); + break; + } else { + // Inform ad server we can't find suitable media file for this ad + vast.util.track(ad.errorURLTemplates, {ERRORCODE: 403}); + } + } + } + + if (!player.vastTracker) { + // No pre-roll, start video + player.trigger('adscanceled'); + } + }); + }, + setupVideoDurationWatcher(){ + player.on(player.tech({IWillNotUseThisInPlugins: true}), 'loadedmetadata', function () { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['start']); + var completeDuration = player.duration(); + var seconds = 0; + var passed = 0; + var firedMiddle = false; + var firedfirstQuartile = false; + var firedthirdQuartile = false; + var interval = setInterval(function () { + passed = completeDuration - ++seconds; + if (passed <= completeDuration * (3 / 4) && !firedfirstQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['firstQuartile']); + firedfirstQuartile = true; + } + if (passed <= completeDuration * (1 / 4) && !firedthirdQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['thirdQuartile']); + firedthirdQuartile = true; + } + if (passed <= completeDuration * (1 / 2) && !firedMiddle) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['midpoint']); + firedMiddle = true; + } + if (seconds >= completeDuration) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['complete']); + clearInterval(interval); + } + }, 1000); + }); + }, + setupEvents: function () { + + var errorOccurred = false, + canplayFn = function () { + player.vastTracker.load(); + }, + timeupdateFn = function () { + if (isNaN(player.vastTracker.assetDuration)) { + player.vastTracker.assetDuration = player.duration(); + } + player.vastTracker.setProgress(player.currentTime()); + }, + pauseFn = function () { + player.vastTracker.setPaused(true); + player.one('play', function () { + player.vastTracker.setPaused(false); + }); + }, + errorFn = function () { + // Inform ad server we couldn't play the media file for this ad + vast.util.track(player.vastTracker.ad.errorURLTemplates, {ERRORCODE: 405}); + errorOccurred = true; + player.trigger('ended'); + }; + + player.on('canplay', canplayFn); + player.on('timeupdate', timeupdateFn); + player.on('pause', pauseFn); + player.on('error', errorFn); + + player.one('vast-preroll-removed', function () { + player.off('canplay', canplayFn); + player.off('timeupdate', timeupdateFn); + player.off('pause', pauseFn); + player.off('error', errorFn); + if (!errorOccurred) { + player.vastTracker.complete(); + } + }); + }, + + preroll: function () { + player.ads.startLinearAdMode(); + player.vast.showControls = player.controls(); + if (player.vast.showControls) { + player.controls(false); + } + + // load linear ad sources and start playing them + player.src(player.vast.sources); + + var clickthrough; + if (player.vastTracker.clickThroughURLTemplate) { + clickthrough = vast.util.resolveURLTemplates( + [player.vastTracker.clickThroughURLTemplate], + { + CACHEBUSTER: Math.round(Math.random() * 1.0e+10), + CONTENTPLAYHEAD: player.vastTracker.progressFormated() + } + )[0]; } + var blocker = window.document.createElement("a"); + blocker.className = "vast-blocker"; + blocker.href = clickthrough || "#"; + blocker.target = "_blank"; + blocker.onclick = function () { + if (player.paused()) { + player.play(); + return false; + } + var clicktrackers = player.vastTracker.clickTrackingURLTemplate; + if (clicktrackers) { + player.vastTracker.trackURLs([clicktrackers]); + } + player.trigger("adclick"); + }; + player.vast.blocker = blocker; + player.el().insertBefore(blocker, player.controlBar.el()); + + var skipButton = window.document.createElement("div"); + skipButton.className = "vast-skip-button"; + if (settings.skip < 0) { + skipButton.style.display = "none"; + } + player.vast.skipButton = skipButton; + player.el().appendChild(skipButton); + + player.on("timeupdate", player.vast.timeupdate); + + skipButton.onclick = function (e) { + if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') >= 0) { + player.vastTracker.skip(); + player.vast.tearDown(); + } + if (window.Event.prototype.stopPropagation !== undefined) { + e.stopPropagation(); + } else { + return false; + } + }; + + player.vast.setupEvents(); + + player.one('ended', player.vast.tearDown); - player.vastTracker = new vast.tracker(ad, creative); + player.trigger('vast-preroll-ready'); + }, - foundCreative = true; - } + tearDown: function () { + // remove preroll buttons + player.vast.skipButton.parentNode.removeChild(player.vast.skipButton); + player.vast.blocker.parentNode.removeChild(player.vast.blocker); - } else if (creative.type === "companion" && !foundCompanion) { + // remove vast-specific events + player.off('timeupdate', player.vast.timeupdate); + player.off('ended', player.vast.tearDown); - player.vast.companion = creative; + // end ad mode + player.ads.endLinearAdMode(); - foundCompanion = true; + // show player controls for video + if (player.vast.showControls) { + player.controls(true); + } + player.trigger('vast-preroll-removed'); + }, + + timeupdate: function (e) { + player.loadingSpinner.el().style.display = "none"; + var timeLeft = Math.ceil(settings.skip - player.currentTime()); + if (timeLeft > 0) { + player.vast.skipButton.innerHTML = "Skip in " + timeLeft + "..."; + } else { + if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') === -1) { + player.vast.skipButton.className += " enabled"; + player.vast.skipButton.innerHTML = "Skip"; + } + } } - } - - if (player.vastTracker) { - // vast tracker and content is ready to go, trigger event - player.trigger('vast-ready'); - break; - } else { - // Inform ad server we can't find suitable media file for this ad - vast.util.track(ad.errorURLTemplates, {ERRORCODE: 403}); - } - } - } - - if (!player.vastTracker) { - // No pre-roll, start video - player.trigger('adscanceled'); - } - }); - }, - - setupEvents: function() { - - var errorOccurred = false, - canplayFn = function() { - player.vastTracker.load(); - }, - timeupdateFn = function() { - if (isNaN(player.vastTracker.assetDuration)) { - player.vastTracker.assetDuration = player.duration(); - } - player.vastTracker.setProgress(player.currentTime()); - }, - pauseFn = function() { - player.vastTracker.setPaused(true); - player.one('play', function(){ - player.vastTracker.setPaused(false); - }); - }, - errorFn = function() { - // Inform ad server we couldn't play the media file for this ad - vast.util.track(player.vastTracker.ad.errorURLTemplates, {ERRORCODE: 405}); - errorOccurred = true; - player.trigger('ended'); }; - player.on('canplay', canplayFn); - player.on('timeupdate', timeupdateFn); - player.on('pause', pauseFn); - player.on('error', errorFn); - - player.one('vast-preroll-removed', function() { - player.off('canplay', canplayFn); - player.off('timeupdate', timeupdateFn); - player.off('pause', pauseFn); - player.off('error', errorFn); - if (!errorOccurred) { - player.vastTracker.complete(); - } - }); - }, - - preroll: function() { - player.ads.startLinearAdMode(); - player.vast.showControls = player.controls(); - if (player.vast.showControls) { - player.controls(false); - } - - // load linear ad sources and start playing them - player.src(player.vast.sources); - - var clickthrough; - if (player.vastTracker.clickThroughURLTemplate) { - clickthrough = vast.util.resolveURLTemplates( - [player.vastTracker.clickThroughURLTemplate], - { - CACHEBUSTER: Math.round(Math.random() * 1.0e+10), - CONTENTPLAYHEAD: player.vastTracker.progressFormated() + }, + + vastPlugin = function (options) { + var player = this; + var settings = extend({}, defaults, options || {}); + + // check that we have the ads plugin + if (player.ads === undefined) { + window.console.error('vast video plugin requires videojs-contrib-ads, vast plugin not initialized'); + return null; } - )[0]; - } - var blocker = window.document.createElement("a"); - blocker.className = "vast-blocker"; - blocker.href = clickthrough || "#"; - blocker.target = "_blank"; - blocker.onclick = function() { - if (player.paused()) { - player.play(); - return false; - } - var clicktrackers = player.vastTracker.clickTrackingURLTemplate; - if (clicktrackers) { - player.vastTracker.trackURLs([clicktrackers]); - } - player.trigger("adclick"); - }; - player.vast.blocker = blocker; - player.el().insertBefore(blocker, player.controlBar.el()); - - var skipButton = window.document.createElement("div"); - skipButton.className = "vast-skip-button"; - if (settings.skip < 0) { - skipButton.style.display = "none"; - } - player.vast.skipButton = skipButton; - player.el().appendChild(skipButton); - - player.on("timeupdate", player.vast.timeupdate); - - skipButton.onclick = function(e) { - if((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') >= 0) { - player.vastTracker.skip(); - player.vast.tearDown(); - } - if(window.Event.prototype.stopPropagation !== undefined) { - e.stopPropagation(); - } else { - return false; - } + + // set up vast plugin, then set up events here + player.vast = new Vast(player, settings); + + player.on('vast-ready', function () { + // vast is prepared with content, set up ads and trigger ready function + player.trigger('adsready'); + }); + + player.on('vast-preroll-ready', function () { + // start playing preroll, note: this should happen this way no matter what, even if autoplay + // has been disabled since the preroll function shouldn't run until the user/autoplay has + // caused the main video to trigger this preroll function + player.play(); + }); + + player.on('vast-preroll-removed', function () { + // preroll done or removed, start playing the actual video + player.play(); + }); + + player.on('contentupdate', function () { + // videojs-ads triggers this when src changes + player.vast.getContent(settings.url); + }); + + player.on('readyforpreroll', function () { + // if we don't have a vast url, just bail out + if (!settings.url) { + player.trigger('adscanceled'); + return null; + } + // set up and start playing preroll + player.vast.preroll(); + }); + + // make an ads request immediately so we're ready when the viewer hits "play" + if (player.currentSrc()) { + player.vast.getContent(settings.url); + } + + // return player to allow this plugin to be chained + return player; }; - player.vast.setupEvents(); - - player.one('ended', player.vast.tearDown); - - player.trigger('vast-preroll-ready'); - }, - - tearDown: function() { - // remove preroll buttons - player.vast.skipButton.parentNode.removeChild(player.vast.skipButton); - player.vast.blocker.parentNode.removeChild(player.vast.blocker); - - // remove vast-specific events - player.off('timeupdate', player.vast.timeupdate); - player.off('ended', player.vast.tearDown); - - // end ad mode - player.ads.endLinearAdMode(); - - // show player controls for video - if (player.vast.showControls) { - player.controls(true); - } - - player.trigger('vast-preroll-removed'); - }, - - timeupdate: function(e) { - player.loadingSpinner.el().style.display = "none"; - var timeLeft = Math.ceil(settings.skip - player.currentTime()); - if(timeLeft > 0) { - player.vast.skipButton.innerHTML = "Skip in " + timeLeft + "..."; - } else { - if((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') === -1){ - player.vast.skipButton.className += " enabled"; - player.vast.skipButton.innerHTML = "Skip"; - } - } - } - }; - - }, - - vastPlugin = function(options) { - var player = this; - var settings = extend({}, defaults, options || {}); - - // check that we have the ads plugin - if (player.ads === undefined) { - window.console.error('vast video plugin requires videojs-contrib-ads, vast plugin not initialized'); - return null; - } - - // set up vast plugin, then set up events here - player.vast = new Vast(player, settings); - - player.on('vast-ready', function () { - // vast is prepared with content, set up ads and trigger ready function - player.trigger('adsready'); - }); - - player.on('vast-preroll-ready', function () { - // start playing preroll, note: this should happen this way no matter what, even if autoplay - // has been disabled since the preroll function shouldn't run until the user/autoplay has - // caused the main video to trigger this preroll function - player.play(); - }); - - player.on('vast-preroll-removed', function () { - // preroll done or removed, start playing the actual video - player.play(); - }); - - player.on('contentupdate', function(){ - // videojs-ads triggers this when src changes - player.vast.getContent(settings.url); - }); - - player.on('readyforpreroll', function() { - // if we don't have a vast url, just bail out - if (!settings.url) { - player.trigger('adscanceled'); - return null; - } - // set up and start playing preroll - player.vast.preroll(); - }); - - // make an ads request immediately so we're ready when the viewer hits "play" - if (player.currentSrc()) { - player.vast.getContent(settings.url); - } - - // return player to allow this plugin to be chained - return player; - }; - - vjs.plugin('vast', vastPlugin); + vjs.plugin('vast', vastPlugin); }(window, videojs, DMVAST)); From 49f2f01581c2eb39bff9a0b0c7423f1f6ae461b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Wed, 27 Jul 2016 11:09:38 -0400 Subject: [PATCH 2/7] Code refactoring --- videojs.vast.js | 74 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/videojs.vast.js b/videojs.vast.js index 4521900..88460d5 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -67,8 +67,16 @@ } return sources; }, + /* + Makes GET request under specified url. + @param links Array of url strings + */ callTriggerLink(links){ var request = new XMLHttpRequest(); + + if (!links || !links.length) { + return; + } links.forEach(function (link) { request.open("GET", link); request.send(); @@ -109,7 +117,8 @@ } if (player.vastTracker) { - player.vast.setupVideoDurationWatcher(); + // setup video duration watching to fire events as video time goes by + player.vast.controlTrackingEvents(); // vast tracker and content is ready to go, trigger event player.trigger('vast-ready'); break; @@ -126,31 +135,48 @@ } }); }, - setupVideoDurationWatcher(){ + /* + Observe video and request tracking events. + */ + controlTrackingEvents(){ + var completeDuration, seconds = 0, passed = 0, firedMiddle, firedFirstQuartile, firedThirdQuartile, interval; + /** + * Need to wait until video meta data is loaded to get video duration. + * To get tech argument that is required for this function, i need to pass 'IWillNotUseThisInPlugins'. + * See https://github.com/videojs/video.js/issues/2617 + */ player.on(player.tech({IWillNotUseThisInPlugins: true}), 'loadedmetadata', function () { player.vast.callTriggerLink(player.vastTracker.trackingEvents['start']); - var completeDuration = player.duration(); - var seconds = 0; - var passed = 0; - var firedMiddle = false; - var firedfirstQuartile = false; - var firedthirdQuartile = false; - var interval = setInterval(function () { - passed = completeDuration - ++seconds; - if (passed <= completeDuration * (3 / 4) && !firedfirstQuartile) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['firstQuartile']); - firedfirstQuartile = true; - } - if (passed <= completeDuration * (1 / 4) && !firedthirdQuartile) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['thirdQuartile']); - firedthirdQuartile = true; - } - if (passed <= completeDuration * (1 / 2) && !firedMiddle) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['midpoint']); - firedMiddle = true; - } - if (seconds >= completeDuration) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['complete']); + completeDuration = player.duration(); + if (!completeDuration) { + window.console.error("We couldn't obtain video duration."); + return; + } + interval = setInterval(function () { + try { + passed = completeDuration - ++seconds; + // First Quartile. + if (passed <= completeDuration * 0.75 && !firedFirstQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['firstQuartile']); + firedFirstQuartile = true; + } + // Midpoint. + if (passed <= completeDuration * 0.5 && !firedMiddle) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['midpoint']); + firedMiddle = true; + } + // Third Quartile. + if (passed <= completeDuration * 0.25 && !firedThirdQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['thirdQuartile']); + firedThirdQuartile = true; + } + // Videoo ended. + if (seconds >= completeDuration) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents['complete']); + clearInterval(interval); + } + } catch (error) { + window.console.error('Error during firing tracking events.'); clearInterval(interval); } }, 1000); From 83fab0fa01a8773bbaa6b4d0b6cad08bf41db945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Fri, 29 Jul 2016 14:06:29 -0400 Subject: [PATCH 3/7] fixed problem that prevented from webpack minifying --- videojs.vast.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/videojs.vast.js b/videojs.vast.js index 88460d5..7f75551 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -71,7 +71,7 @@ Makes GET request under specified url. @param links Array of url strings */ - callTriggerLink(links){ + callTriggerLink: function (links) { var request = new XMLHttpRequest(); if (!links || !links.length) { @@ -138,7 +138,7 @@ /* Observe video and request tracking events. */ - controlTrackingEvents(){ + controlTrackingEvents: function () { var completeDuration, seconds = 0, passed = 0, firedMiddle, firedFirstQuartile, firedThirdQuartile, interval; /** * Need to wait until video meta data is loaded to get video duration. From 6bee8df7b0d3056fd06222058f8b09a0645aa9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Wed, 14 Sep 2016 18:19:39 +0200 Subject: [PATCH 4/7] fixed mega bug did not checked if video is present, fired events even if we killed video --- videojs.vast.js | 724 ++++++++++++++++++++++++------------------------ 1 file changed, 367 insertions(+), 357 deletions(-) diff --git a/videojs.vast.js b/videojs.vast.js index 7f75551..73069dc 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -1,385 +1,395 @@ (function (window, vjs, vast) { - 'use strict'; - var extend = function (obj) { - var arg, i, k; - for (i = 1; i < arguments.length; i++) { - arg = arguments[i]; - for (k in arg) { - if (arg.hasOwnProperty(k)) { - obj[k] = arg[k]; - } + 'use strict'; + var extend = function (obj) { + var arg, i, k; + for (i = 1; i < arguments.length; i++) { + arg = arguments[ i ]; + for (k in arg) { + if (arg.hasOwnProperty(k)) { + obj[ k ] = arg[ k ]; + } + } + } + return obj; + }, + + defaults = { + // seconds before skip button shows, negative values to disable skip button altogether + skip: 5 + }, + + Vast = function (player, settings) { + + // return vast plugin + return { + createSourceObjects: function (media_files) { + var sourcesByFormat = {}, i, j, tech; + var techOrder = player.options().techOrder; + for (i = 0, j = techOrder.length; i < j; i++) { + var techName = techOrder[ i ].charAt(0).toUpperCase() + techOrder[ i ].slice(1); + tech = window.videojs[ techName ]; + + // Check if the current tech is defined before continuing + if (!tech) { + continue; + } + + // Check if the browser supports this technology + if (tech.isSupported()) { + // Loop through each source object + for (var a = 0, b = media_files.length; a < b; a++) { + var media_file = media_files[ a ]; + var source = { type: media_file.mimeType, src: media_file.fileURL }; + // Check if source can be played with this technology + if (tech.canPlaySource(source)) { + if (sourcesByFormat[ techOrder[ i ] ] === undefined) { + sourcesByFormat[ techOrder[ i ] ] = []; + } + sourcesByFormat[ techOrder[ i ] ].push({ + type: media_file.mimeType, + src: media_file.fileURL, + width: media_file.width, + height: media_file.height + }); } + } } - return obj; + } + // Create sources in preferred format order + var sources = []; + for (j = 0; j < techOrder.length; j++) { + tech = techOrder[ j ]; + if (sourcesByFormat[ tech ] !== undefined) { + for (i = 0; i < sourcesByFormat[ tech ].length; i++) { + sources.push(sourcesByFormat[ tech ][ i ]); + } + } + } + return sources; }, - - defaults = { - // seconds before skip button shows, negative values to disable skip button altogether - skip: 5 + /* + Makes GET request under specified url. + @param links Array of url strings + */ + callTriggerLink: function (links) { + var request = new XMLHttpRequest(); + + if (!links || !links.length) { + return; + } + links.forEach(function (link) { + request.open("GET", link); + request.send(); + }); }, - - Vast = function (player, settings) { - - // return vast plugin - return { - createSourceObjects: function (media_files) { - var sourcesByFormat = {}, i, j, tech; - var techOrder = player.options().techOrder; - for (i = 0, j = techOrder.length; i < j; i++) { - var techName = techOrder[i].charAt(0).toUpperCase() + techOrder[i].slice(1); - tech = window.videojs[techName]; - - // Check if the current tech is defined before continuing - if (!tech) { - continue; - } - - // Check if the browser supports this technology - if (tech.isSupported()) { - // Loop through each source object - for (var a = 0, b = media_files.length; a < b; a++) { - var media_file = media_files[a]; - var source = {type: media_file.mimeType, src: media_file.fileURL}; - // Check if source can be played with this technology - if (tech.canPlaySource(source)) { - if (sourcesByFormat[techOrder[i]] === undefined) { - sourcesByFormat[techOrder[i]] = []; - } - sourcesByFormat[techOrder[i]].push({ - type: media_file.mimeType, - src: media_file.fileURL, - width: media_file.width, - height: media_file.height - }); - } - } - } - } - // Create sources in preferred format order - var sources = []; - for (j = 0; j < techOrder.length; j++) { - tech = techOrder[j]; - if (sourcesByFormat[tech] !== undefined) { - for (i = 0; i < sourcesByFormat[tech].length; i++) { - sources.push(sourcesByFormat[tech][i]); - } - } - } - return sources; - }, - /* - Makes GET request under specified url. - @param links Array of url strings - */ - callTriggerLink: function (links) { - var request = new XMLHttpRequest(); - - if (!links || !links.length) { + getContent: function () { + // query vast url given in settings + vast.client.get(settings.url, function (response) { + if (response) { + // we got a response, deal with it + for (var adIdx = 0; adIdx < response.ads.length; adIdx++) { + var ad = response.ads[ adIdx ]; + player.vast.companion = undefined; + for (var creaIdx = 0; creaIdx < ad.creatives.length; creaIdx++) { + var creative = ad.creatives[ creaIdx ], foundCreative = false, foundCompanion = false; + if (creative.type === "linear" && !foundCreative) { + + if (creative.mediaFiles.length) { + + player.vast.sources = player.vast.createSourceObjects(creative.mediaFiles); + + if (!player.vast.sources.length) { + player.trigger('adscanceled'); return; - } - links.forEach(function (link) { - request.open("GET", link); - request.send(); - }); - }, - getContent: function () { - // query vast url given in settings - vast.client.get(settings.url, function (response) { - if (response) { - // we got a response, deal with it - for (var adIdx = 0; adIdx < response.ads.length; adIdx++) { - var ad = response.ads[adIdx]; - player.vast.companion = undefined; - for (var creaIdx = 0; creaIdx < ad.creatives.length; creaIdx++) { - var creative = ad.creatives[creaIdx], foundCreative = false, foundCompanion = false; - if (creative.type === "linear" && !foundCreative) { - - if (creative.mediaFiles.length) { - - player.vast.sources = player.vast.createSourceObjects(creative.mediaFiles); - - if (!player.vast.sources.length) { - player.trigger('adscanceled'); - return; - } - - player.vastTracker = new vast.tracker(ad, creative); - foundCreative = true; - } - - } else if (creative.type === "companion" && !foundCompanion) { - - player.vast.companion = creative; - - foundCompanion = true; - - } - } - - if (player.vastTracker) { - // setup video duration watching to fire events as video time goes by - player.vast.controlTrackingEvents(); - // vast tracker and content is ready to go, trigger event - player.trigger('vast-ready'); - break; - } else { - // Inform ad server we can't find suitable media file for this ad - vast.util.track(ad.errorURLTemplates, {ERRORCODE: 403}); - } - } - } - - if (!player.vastTracker) { - // No pre-roll, start video - player.trigger('adscanceled'); - } - }); - }, - /* - Observe video and request tracking events. - */ - controlTrackingEvents: function () { - var completeDuration, seconds = 0, passed = 0, firedMiddle, firedFirstQuartile, firedThirdQuartile, interval; - /** - * Need to wait until video meta data is loaded to get video duration. - * To get tech argument that is required for this function, i need to pass 'IWillNotUseThisInPlugins'. - * See https://github.com/videojs/video.js/issues/2617 - */ - player.on(player.tech({IWillNotUseThisInPlugins: true}), 'loadedmetadata', function () { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['start']); - completeDuration = player.duration(); - if (!completeDuration) { - window.console.error("We couldn't obtain video duration."); - return; - } - interval = setInterval(function () { - try { - passed = completeDuration - ++seconds; - // First Quartile. - if (passed <= completeDuration * 0.75 && !firedFirstQuartile) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['firstQuartile']); - firedFirstQuartile = true; - } - // Midpoint. - if (passed <= completeDuration * 0.5 && !firedMiddle) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['midpoint']); - firedMiddle = true; - } - // Third Quartile. - if (passed <= completeDuration * 0.25 && !firedThirdQuartile) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['thirdQuartile']); - firedThirdQuartile = true; - } - // Videoo ended. - if (seconds >= completeDuration) { - player.vast.callTriggerLink(player.vastTracker.trackingEvents['complete']); - clearInterval(interval); - } - } catch (error) { - window.console.error('Error during firing tracking events.'); - clearInterval(interval); - } - }, 1000); - }); - }, - setupEvents: function () { - - var errorOccurred = false, - canplayFn = function () { - player.vastTracker.load(); - }, - timeupdateFn = function () { - if (isNaN(player.vastTracker.assetDuration)) { - player.vastTracker.assetDuration = player.duration(); - } - player.vastTracker.setProgress(player.currentTime()); - }, - pauseFn = function () { - player.vastTracker.setPaused(true); - player.one('play', function () { - player.vastTracker.setPaused(false); - }); - }, - errorFn = function () { - // Inform ad server we couldn't play the media file for this ad - vast.util.track(player.vastTracker.ad.errorURLTemplates, {ERRORCODE: 405}); - errorOccurred = true; - player.trigger('ended'); - }; - - player.on('canplay', canplayFn); - player.on('timeupdate', timeupdateFn); - player.on('pause', pauseFn); - player.on('error', errorFn); - - player.one('vast-preroll-removed', function () { - player.off('canplay', canplayFn); - player.off('timeupdate', timeupdateFn); - player.off('pause', pauseFn); - player.off('error', errorFn); - if (!errorOccurred) { - player.vastTracker.complete(); - } - }); - }, - - preroll: function () { - player.ads.startLinearAdMode(); - player.vast.showControls = player.controls(); - if (player.vast.showControls) { - player.controls(false); - } + } - // load linear ad sources and start playing them - player.src(player.vast.sources); - - var clickthrough; - if (player.vastTracker.clickThroughURLTemplate) { - clickthrough = vast.util.resolveURLTemplates( - [player.vastTracker.clickThroughURLTemplate], - { - CACHEBUSTER: Math.round(Math.random() * 1.0e+10), - CONTENTPLAYHEAD: player.vastTracker.progressFormated() - } - )[0]; + player.vastTracker = new vast.tracker(ad, creative); + foundCreative = true; } - var blocker = window.document.createElement("a"); - blocker.className = "vast-blocker"; - blocker.href = clickthrough || "#"; - blocker.target = "_blank"; - blocker.onclick = function () { - if (player.paused()) { - player.play(); - return false; - } - var clicktrackers = player.vastTracker.clickTrackingURLTemplate; - if (clicktrackers) { - player.vastTracker.trackURLs([clicktrackers]); - } - player.trigger("adclick"); - }; - player.vast.blocker = blocker; - player.el().insertBefore(blocker, player.controlBar.el()); - - var skipButton = window.document.createElement("div"); - skipButton.className = "vast-skip-button"; - if (settings.skip < 0) { - skipButton.style.display = "none"; - } - player.vast.skipButton = skipButton; - player.el().appendChild(skipButton); - player.on("timeupdate", player.vast.timeupdate); + } else if (creative.type === "companion" && !foundCompanion) { - skipButton.onclick = function (e) { - if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') >= 0) { - player.vastTracker.skip(); - player.vast.tearDown(); - } - if (window.Event.prototype.stopPropagation !== undefined) { - e.stopPropagation(); - } else { - return false; - } - }; + player.vast.companion = creative; - player.vast.setupEvents(); + foundCompanion = true; - player.one('ended', player.vast.tearDown); + } + } - player.trigger('vast-preroll-ready'); - }, + if (player.vastTracker) { + // setup video duration watching to fire events as video time goes by + player.vast.controlTrackingEvents(); + // vast tracker and content is ready to go, trigger event + player.trigger('vast-ready'); + break; + } else { + // Inform ad server we can't find suitable media file for this ad + vast.util.track(ad.errorURLTemplates, { ERRORCODE: 403 }); + } + } + } - tearDown: function () { - // remove preroll buttons - player.vast.skipButton.parentNode.removeChild(player.vast.skipButton); - player.vast.blocker.parentNode.removeChild(player.vast.blocker); + if (!player.vastTracker) { + // No pre-roll, start video + player.trigger('adscanceled'); + } + }); + }, + /* + Observe video and request tracking events. + */ + controlTrackingEvents: function () { + var completeDuration, passed = 0, firedMiddle, firedFirstQuartile, firedThirdQuartile, interval; + let isPlayerStillAlive = true + /** + * Need to wait until video meta data is loaded to get video duration. + * To get tech argument that is required for this function, i need to pass 'IWillNotUseThisInPlugins'. + * See https://github.com/videojs/video.js/issues/2617 + */ + player.on(player.tech({ IWillNotUseThisInPlugins: true }), 'loadedmetadata', function () { + player.vast.callTriggerLink(player.vastTracker.trackingEvents[ 'start' ]); + completeDuration = player.duration(); + if (!completeDuration) { + window.console.error("We couldn't obtain video duration."); + return; + } + interval = setInterval(function () { + + try { + player.currentTime() + } catch (e) { + isPlayerStillAlive = false + } + if (!isPlayerStillAlive) { + clearInterval(interval); + } + try { + passed = player.duration().toFixed(0) - Math.ceil(player.currentTime()) + // First Quartile. + if (passed <= completeDuration * 0.75 && !firedFirstQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents[ 'firstQuartile' ]); + firedFirstQuartile = true; + } + // Midpoint. + if (passed <= completeDuration * 0.5 && !firedMiddle) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents[ 'midpoint' ]); + firedMiddle = true; + } + // Third Quartile. + if (passed <= completeDuration * 0.25 && !firedThirdQuartile) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents[ 'thirdQuartile' ]); + firedThirdQuartile = true; + } + // Videoo ended. + if (completeDuration - passed >= completeDuration) { + player.vast.callTriggerLink(player.vastTracker.trackingEvents[ 'complete' ]); + clearInterval(interval); + } + } catch (error) { + window.console.error('Error during firing tracking events.', error); + clearInterval(interval); + } + }, 700); + }); + }, + setupEvents: function () { + + var errorOccurred = false, + canplayFn = function () { + player.vastTracker.load(); + }, + timeupdateFn = function () { + if (isNaN(player.vastTracker.assetDuration)) { + player.vastTracker.assetDuration = player.duration(); + } + player.vastTracker.setProgress(player.currentTime()); + }, + pauseFn = function () { + player.vastTracker.setPaused(true); + player.one('play', function () { + player.vastTracker.setPaused(false); + }); + }, + errorFn = function () { + // Inform ad server we couldn't play the media file for this ad + vast.util.track(player.vastTracker.ad.errorURLTemplates, { ERRORCODE: 405 }); + errorOccurred = true; + player.trigger('ended'); + }; - // remove vast-specific events - player.off('timeupdate', player.vast.timeupdate); - player.off('ended', player.vast.tearDown); + player.on('canplay', canplayFn); + player.on('timeupdate', timeupdateFn); + player.on('pause', pauseFn); + player.on('error', errorFn); + + player.one('vast-preroll-removed', function () { + player.off('canplay', canplayFn); + player.off('timeupdate', timeupdateFn); + player.off('pause', pauseFn); + player.off('error', errorFn); + if (!errorOccurred) { + player.vastTracker.complete(); + } + }); + }, - // end ad mode - player.ads.endLinearAdMode(); + preroll: function () { + player.ads.startLinearAdMode(); + player.vast.showControls = player.controls(); + if (player.vast.showControls) { + player.controls(false); + } + + // load linear ad sources and start playing them + player.src(player.vast.sources); + + var clickthrough; + if (player.vastTracker.clickThroughURLTemplate) { + clickthrough = vast.util.resolveURLTemplates( + [ player.vastTracker.clickThroughURLTemplate ], + { + CACHEBUSTER: Math.round(Math.random() * 1.0e+10), + CONTENTPLAYHEAD: player.vastTracker.progressFormated() + } + )[ 0 ]; + } + var blocker = window.document.createElement("a"); + blocker.className = "vast-blocker"; + blocker.href = clickthrough || "#"; + blocker.target = "_blank"; + blocker.onclick = function () { + if (player.paused()) { + player.play(); + return false; + } + var clicktrackers = player.vastTracker.clickTrackingURLTemplate; + if (clicktrackers) { + player.vastTracker.trackURLs([ clicktrackers ]); + } + player.trigger("adclick"); + }; + player.vast.blocker = blocker; + player.el().insertBefore(blocker, player.controlBar.el()); + + var skipButton = window.document.createElement("div"); + skipButton.className = "vast-skip-button"; + if (settings.skip < 0) { + skipButton.style.display = "none"; + } + player.vast.skipButton = skipButton; + player.el().appendChild(skipButton); + + player.on("timeupdate", player.vast.timeupdate); + + skipButton.onclick = function (e) { + if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') >= 0) { + player.vastTracker.skip(); + player.vast.tearDown(); + } + if (window.Event.prototype.stopPropagation !== undefined) { + e.stopPropagation(); + } else { + return false; + } + }; - // show player controls for video - if (player.vast.showControls) { - player.controls(true); - } + player.vast.setupEvents(); - player.trigger('vast-preroll-removed'); - }, - - timeupdate: function (e) { - player.loadingSpinner.el().style.display = "none"; - var timeLeft = Math.ceil(settings.skip - player.currentTime()); - if (timeLeft > 0) { - player.vast.skipButton.innerHTML = "Skip in " + timeLeft + "..."; - } else { - if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') === -1) { - player.vast.skipButton.className += " enabled"; - player.vast.skipButton.innerHTML = "Skip"; - } - } - } - }; + player.one('ended', player.vast.tearDown); + player.trigger('vast-preroll-ready'); }, - vastPlugin = function (options) { - var player = this; - var settings = extend({}, defaults, options || {}); + tearDown: function () { + // remove preroll buttons + player.vast.skipButton.parentNode.removeChild(player.vast.skipButton); + player.vast.blocker.parentNode.removeChild(player.vast.blocker); - // check that we have the ads plugin - if (player.ads === undefined) { - window.console.error('vast video plugin requires videojs-contrib-ads, vast plugin not initialized'); - return null; - } + // remove vast-specific events + player.off('timeupdate', player.vast.timeupdate); + player.off('ended', player.vast.tearDown); - // set up vast plugin, then set up events here - player.vast = new Vast(player, settings); - - player.on('vast-ready', function () { - // vast is prepared with content, set up ads and trigger ready function - player.trigger('adsready'); - }); - - player.on('vast-preroll-ready', function () { - // start playing preroll, note: this should happen this way no matter what, even if autoplay - // has been disabled since the preroll function shouldn't run until the user/autoplay has - // caused the main video to trigger this preroll function - player.play(); - }); - - player.on('vast-preroll-removed', function () { - // preroll done or removed, start playing the actual video - player.play(); - }); - - player.on('contentupdate', function () { - // videojs-ads triggers this when src changes - player.vast.getContent(settings.url); - }); - - player.on('readyforpreroll', function () { - // if we don't have a vast url, just bail out - if (!settings.url) { - player.trigger('adscanceled'); - return null; - } - // set up and start playing preroll - player.vast.preroll(); - }); + // end ad mode + player.ads.endLinearAdMode(); - // make an ads request immediately so we're ready when the viewer hits "play" - if (player.currentSrc()) { - player.vast.getContent(settings.url); - } + // show player controls for video + if (player.vast.showControls) { + player.controls(true); + } - // return player to allow this plugin to be chained - return player; - }; + player.trigger('vast-preroll-removed'); + }, - vjs.plugin('vast', vastPlugin); + timeupdate: function (e) { + player.loadingSpinner.el().style.display = "none"; + var timeLeft = Math.ceil(settings.skip - player.currentTime()); + if (timeLeft > 0) { + player.vast.skipButton.innerHTML = "Skip in " + timeLeft + "..."; + } else { + if ((' ' + player.vast.skipButton.className + ' ').indexOf(' enabled ') === -1) { + player.vast.skipButton.className += " enabled"; + player.vast.skipButton.innerHTML = "Skip"; + } + } + } + }; + + }, + + vastPlugin = function (options) { + var player = this; + var settings = extend({}, defaults, options || {}); + + // check that we have the ads plugin + if (player.ads === undefined) { + window.console.error('vast video plugin requires videojs-contrib-ads, vast plugin not initialized'); + return null; + } + + // set up vast plugin, then set up events here + player.vast = new Vast(player, settings); + + player.on('vast-ready', function () { + // vast is prepared with content, set up ads and trigger ready function + player.trigger('adsready'); + }); + + player.on('vast-preroll-ready', function () { + // start playing preroll, note: this should happen this way no matter what, even if autoplay + // has been disabled since the preroll function shouldn't run until the user/autoplay has + // caused the main video to trigger this preroll function + player.play(); + }); + + player.on('vast-preroll-removed', function () { + // preroll done or removed, start playing the actual video + player.play(); + }); + + player.on('contentupdate', function () { + // videojs-ads triggers this when src changes + player.vast.getContent(settings.url); + }); + + player.on('readyforpreroll', function () { + // if we don't have a vast url, just bail out + if (!settings.url) { + player.trigger('adscanceled'); + return null; + } + // set up and start playing preroll + player.vast.preroll(); + }); + + // make an ads request immediately so we're ready when the viewer hits "play" + if (player.currentSrc()) { + player.vast.getContent(settings.url); + } + + // return player to allow this plugin to be chained + return player; + }; + + vjs.plugin('vast', vastPlugin); }(window, videojs, DMVAST)); From 7b91b008b46177ed1c29ec7444690b761976a4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Fri, 16 Sep 2016 12:15:27 +0200 Subject: [PATCH 5/7] fixed fix --- videojs.vast.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/videojs.vast.js b/videojs.vast.js index 73069dc..d15f4c1 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -154,12 +154,7 @@ return; } interval = setInterval(function () { - - try { - player.currentTime() - } catch (e) { - isPlayerStillAlive = false - } + isPlayerStillAlive = player.el_ ? true : false if (!isPlayerStillAlive) { clearInterval(interval); } From cfc899681127c7c674668b51f06d852eee9cc302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Tue, 20 Sep 2016 12:39:45 +0200 Subject: [PATCH 6/7] fixed issue that bugged UglifyJS --- videojs.vast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/videojs.vast.js b/videojs.vast.js index d15f4c1..4276ea0 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -140,7 +140,7 @@ */ controlTrackingEvents: function () { var completeDuration, passed = 0, firedMiddle, firedFirstQuartile, firedThirdQuartile, interval; - let isPlayerStillAlive = true + var isPlayerStillAlive = true; /** * Need to wait until video meta data is loaded to get video duration. * To get tech argument that is required for this function, i need to pass 'IWillNotUseThisInPlugins'. From 29f70334ae3681fb021e44ff56eac49bc3ef2d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C5=82aby?= Date: Thu, 22 Sep 2016 12:36:38 +0200 Subject: [PATCH 7/7] Removed irritating log that happened when video was cancelled in middle of function --- videojs.vast.js | 1 - 1 file changed, 1 deletion(-) diff --git a/videojs.vast.js b/videojs.vast.js index 4276ea0..fd7c0c4 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -181,7 +181,6 @@ clearInterval(interval); } } catch (error) { - window.console.error('Error during firing tracking events.', error); clearInterval(interval); } }, 700);