From 6b2d6dd84b97ba7c5128c13f6c916f7e71737184 Mon Sep 17 00:00:00 2001 From: Chris Sinchok Date: Fri, 15 Aug 2014 11:46:20 -0500 Subject: [PATCH] Fix for iPad --- lib/videojs-contrib-ads/videojs.ads.css | 16 + lib/videojs-contrib-ads/videojs.ads.js | 507 ++++++++++++++++++++++++ videojs.vast.js | 12 +- 3 files changed, 534 insertions(+), 1 deletion(-) create mode 100755 lib/videojs-contrib-ads/videojs.ads.css create mode 100755 lib/videojs-contrib-ads/videojs.ads.js diff --git a/lib/videojs-contrib-ads/videojs.ads.css b/lib/videojs-contrib-ads/videojs.ads.css new file mode 100755 index 0000000..6b5b74b --- /dev/null +++ b/lib/videojs-contrib-ads/videojs.ads.css @@ -0,0 +1,16 @@ +/** + * videojs.ads.css + */ + +/* Ad playback */ +.vjs-ad-playing .vjs-progress-control { + pointer-events: none; +} +.vjs-ad-playing .vjs-play-progress { + background-color: #ffe400; +} + +/* Ad loading */ +.vjs-ad-loading .vjs-loading-spinner { + display: block; +} diff --git a/lib/videojs-contrib-ads/videojs.ads.js b/lib/videojs-contrib-ads/videojs.ads.js new file mode 100755 index 0000000..c1628be --- /dev/null +++ b/lib/videojs-contrib-ads/videojs.ads.js @@ -0,0 +1,507 @@ +/** + * Basic Ad support plugin for video.js. + * + * Common code to support ad integrations. + */ +(function(window, document, vjs, undefined) { +"use strict"; + +var + + /** + * Copies properties from one or more objects onto an original. + */ + extend = function(obj /*, arg1, arg2, ... */) { + var arg, i, k; + for (i=1; i 0) { + // if some period of the video is seekable, resume playback + resume(); + return; + } + + // delay a bit and then check again unless we're out of attempts + if (attempts--) { + setTimeout(tryToResume, 50); + } + }; + + if (snapshot.nativePoster) { + tech.poster = snapshot.nativePoster; + } + + // with a custom ad display or burned-in ads, the content player state + // hasn't been modified and so no restoration is required + if (player.currentSrc() === snapshot.src) { + player.play(); + return; + } + + player.src(snapshot.src); + // safari requires a call to `load` to pick up a changed source + player.load(); + + player.one('loadedmetadata', tryToResume); + }, + + /** + * Remove the poster attribute from the video element tech, if present. When + * reusing a video element for multiple videos, the poster image will briefly + * reappear while the new source loads. Removing the attribute ahead of time + * prevents the poster from showing up between videos. + * @param {object} player The videojs player object + */ + removeNativePoster = function(player) { + var tech = player.el().querySelector('.vjs-tech'); + if (tech) { + tech.removeAttribute('poster'); + } + }, + + // --------------------------------------------------------------------------- + // Ad Framework + // --------------------------------------------------------------------------- + + // default framework settings + defaults = { + // maximum amount of time in ms to wait to receive `adsready` from the ad + // implementation after play has been requested. Ad implementations are + // expected to load any dynamic libraries and make any requests to determine + // ad policies for a video during this time. + timeout: 5000, + + // maximum amount of time in ms to wait for the ad implementation to start + // linear ad mode after `readyforpreroll` has fired. This is in addition to + // the standard timeout. + prerollTimeout: 100, + + // when truthy, instructs the plugin to output additional information about + // plugin state to the video.js log. On most devices, the video.js log is + // the same as the developer console. + debug: false + }, + + adFramework = function(options) { + var + player = this, + + // merge options and defaults + settings = extend({}, defaults, options || {}), + + fsmHandler; + + // replace the ad initializer with the ad namespace + player.ads = { + state: 'content-set', + + startLinearAdMode: function() { + player.trigger('adstart'); + }, + + endLinearAdMode: function() { + player.trigger('adend'); + } + }; + + fsmHandler = function(event) { + + // Ad Playback State Machine + var + fsm = { + 'content-set': { + events: { + 'adsready': function() { + this.state = 'ads-ready'; + }, + 'play': function() { + this.state = 'ads-ready?'; + this.snapshot = getPlayerSnapshot(player); + cancelContentPlay(player); + + // remove the poster so it doesn't flash between videos + removeNativePoster(player); + } + } + }, + 'ads-ready': { + events: { + 'play': function() { + this.state = 'preroll?'; + cancelContentPlay(player); + } + } + }, + 'preroll?': { + enter: function() { + + // capture current player state snapshot (playing, currentTime, src) + this.snapshot = getPlayerSnapshot(player); + + // remove the poster so it doesn't flash between videos + removeNativePoster(player); + + // change class to show that we're waiting on ads + player.el().className += ' vjs-ad-loading'; + + // schedule an adtimeout event to fire if we waited too long + player.ads.timeout = window.setTimeout(function() { + player.trigger('adtimeout'); + }, settings.prerollTimeout); + + // signal to ad plugin that it's their opportunity to play a preroll + player.trigger('readyforpreroll'); + + }, + leave: function() { + window.clearTimeout(player.ads.timeout); + + clearImmediate(player.ads.cancelPlayTimeout); + player.ads.cancelPlayTimeout = null; + + removeClass(player.el(), 'vjs-ad-loading'); + }, + events: { + 'play': function() { + cancelContentPlay(player); + }, + 'adstart': function() { + this.state = 'ad-playback'; + player.el().className += ' vjs-ad-playing'; + }, + 'adtimeout': function() { + this.state = 'content-playback'; + player.play(); + } + } + }, + 'ads-ready?': { + enter: function() { + player.el().className += ' vjs-ad-loading'; + player.ads.timeout = window.setTimeout(function() { + player.trigger('adtimeout'); + }, settings.timeout); + }, + leave: function() { + window.clearTimeout(player.ads.timeout); + removeClass(player.el(), 'vjs-ad-loading'); + }, + events: { + 'play': function() { + cancelContentPlay(player); + }, + 'adsready': function() { + this.state = 'preroll?'; + }, + 'adtimeout': function() { + this.state = 'ad-timeout-playback'; + } + } + }, + 'ad-timeout-playback': { + enter: function() { + restorePlayerSnapshot(player, this.snapshot); + }, + events: { + 'adsready': function() { + if (player.paused()) { + this.state = 'ads-ready'; + } else { + this.state = 'preroll?'; + } + }, + 'contentupdate': function() { + if (player.paused()) { + this.state = 'content-set'; + } else { + this.state = 'ads-ready?'; + } + } + } + }, + 'ad-playback': { + events: { + 'adend': function() { + this.state = 'content-playback'; + removeClass(player.el(), 'vjs-ad-playing'); + restorePlayerSnapshot(player, this.snapshot); + } + } + }, + 'content-playback': { + events: { + 'adstart': function() { + this.state = 'ad-playback'; + this.snapshot = getPlayerSnapshot(player); + player.el().className += ' vjs-ad-playing'; + + // remove the poster so it doesn't flash between videos + removeNativePoster(player); + }, + 'contentupdate': function() { + if (player.paused()) { + this.state = 'content-set'; + } else { + this.state = 'ads-ready?'; + } + } + } + } + }; + + (function(state) { + + var noop = function() {}; + + // process the current event with a noop default handler + (fsm[state].events[event.type] || noop).apply(player.ads); + + // execute leave/enter callbacks if present + if (state !== player.ads.state) { + (fsm[state].leave || noop).apply(player.ads); + (fsm[player.ads.state].enter || noop).apply(player.ads); + + if (settings.debug) { + videojs.log('ads', state + ' -> ' + player.ads.state); + } + } + + })(player.ads.state); + + }; + + // register for the events we're interested in + on(player, vjs.Html5.Events.concat([ + // events emitted by ad plugin + 'adtimeout', + 'contentupdate', + // events emitted by third party ad implementors + 'adsready', + 'adstart', // startLinearAdMode() + 'adend', // endLinearAdMode() + ]), fsmHandler); + + // implement 'contentupdate' event. + (function(){ + var + // keep track of last src + lastSrc, + // check if a new src has been set, if so, trigger contentupdate + checkSrc = function() { + var src; + if (player.ads.state !== 'ad-playback') { + src = player.currentSrc(); + if (src !== lastSrc) { + player.trigger({ + type: 'contentupdate', + oldValue: lastSrc, + newValue: src + }); + lastSrc = src; + } + } + }; + // loadstart reliably indicates a new src has been set + player.on('loadstart', checkSrc); + // check immediately in case we missed the loadstart + setImmediate(checkSrc); + })(); + + // kick off the fsm + if (!player.paused()) { + // simulate a play event if we're autoplaying + fsmHandler({type:'play'}); + } + + }; + + // register the ad plugin framework + vjs.plugin('ads', adFramework); + +})(window, document, videojs); diff --git a/videojs.vast.js b/videojs.vast.js index 5346b6e..b9b1cbe 100644 --- a/videojs.vast.js +++ b/videojs.vast.js @@ -134,7 +134,10 @@ player.vast.preroll = function() { player.ads.startLinearAdMode(); - + player.vast.showControls = player.controls(); + if (player.vast.showControls ) { + player.controls(false); + } player.autoplay(true); // play your linear ad content var adSources = player.vast.sources; @@ -155,6 +158,10 @@ 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]); @@ -194,6 +201,9 @@ player.off('timeupdate', player.vast.timeupdate); player.off('ended', player.vast.tearDown); player.ads.endLinearAdMode(); + if (player.vast.showControls ) { + player.controls(true); + } }; player.vast.timeupdate = function(e) {