Formatting, events and ad countdown added
This commit is contained in:
parent
1dd5c9efd9
commit
5671235fd9
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.js
vendored
2
demo/dist/demo.js
vendored
@ -1,3 +1,3 @@
|
||||
!function(){"use strict";var e,t,o,i,r,a;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});var e="tab-focus";document.addEventListener("focusout",function(t){t.target.classList.remove(e)}),document.addEventListener("keydown",function(t){9===t.keyCode&&window.setTimeout(function(){document.activeElement.classList.add(e)},0)});var t=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{tagUrl:"http://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480"}});window.player=t;var o=document.querySelectorAll("[data-source]"),i={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;function n(e,t,o){e&&e.classList[o?"add":"remove"](t)}function s(e,a){if(e in i&&(a||e!==r)&&(r.length||e!==i.video)){switch(e){case i.video:t.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case i.audio:t.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case i.youtube:t.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case i.vimeo:t.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}r=e,Array.from(o).forEach(function(e){return n(e.parentElement,"active",!1)}),n(document.querySelector('[data-source="'+e+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+e).removeAttribute("hidden")}}if(Array.from(o).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");s(t),a&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&s(e.state.type)}),a){var c=!r.length;c&&(r=i.video),r in i&&window.history.replaceState({type:r},"",c?"":"#"+r),r!==i.video&&s(r,!0)}}),"plyr.io"===window.location.host&&(e=window,t=document,o="script",i="ga",e.GoogleAnalyticsObject=i,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}();
|
||||
!function(){"use strict";var e,t,o,i;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{tagUrl:"http://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480"}});window.player=e;var t=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},i=window.location.hash.replace("#",""),r=window.history&&window.history.pushState;function a(e,t,o){e&&e.classList[o?"add":"remove"](t)}function n(r,n){if(r in o&&(n||r!==i)&&(i.length||r!==o.video)){switch(r){case o.video:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case o.audio:e.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case o.youtube:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case o.vimeo:e.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}i=r,Array.from(t).forEach(function(e){return a(e.parentElement,"active",!1)}),a(document.querySelector('[data-source="'+r+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+r).removeAttribute("hidden")}}if(Array.from(t).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");n(t),r&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&n(e.state.type)}),r){var s=!i.length;s&&(i=o.video),i in o&&window.history.replaceState({type:i},"",s?"":"#"+i),i!==o.video&&n(i,!0)}}),"plyr.io"===window.location.host&&(e=window,t=document,e.GoogleAnalyticsObject="ga",e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,o=t.createElement("script"),i=t.getElementsByTagName("script")[0],o.async=1,o.src="//www.google-analytics.com/analytics.js",i.parentNode.insertBefore(o,i),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}();
|
||||
|
||||
//# sourceMappingURL=demo.js.map
|
||||
|
2
demo/dist/demo.js.map
vendored
2
demo/dist/demo.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js
vendored
2
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,7 @@
|
||||
// Default config
|
||||
// ==========================================================================
|
||||
// Plyr default config
|
||||
// ==========================================================================
|
||||
|
||||
const defaults = {
|
||||
// Disable
|
||||
enabled: true,
|
||||
@ -176,7 +179,7 @@ const defaults = {
|
||||
reset: 'Reset',
|
||||
none: 'None',
|
||||
disabled: 'Disabled',
|
||||
advertisement: 'Advertisement',
|
||||
adCountdown: 'Ad - {countdown}',
|
||||
},
|
||||
|
||||
// URLs
|
||||
@ -251,6 +254,17 @@ const defaults = {
|
||||
'statechange',
|
||||
'qualitychange',
|
||||
'qualityrequested',
|
||||
|
||||
// Ads
|
||||
'adsloaded',
|
||||
'adscontentpause',
|
||||
'adsconentresume',
|
||||
'adstarted',
|
||||
'adsmidpoint',
|
||||
'adscomplete',
|
||||
'adsallcomplete',
|
||||
'adsimpression',
|
||||
'adsclick',
|
||||
],
|
||||
|
||||
// Selectors
|
||||
|
@ -38,13 +38,16 @@ class Ads {
|
||||
* Get the ads instance ready.
|
||||
*/
|
||||
ready() {
|
||||
this.adsContainer = null;
|
||||
this.adDisplayContainer = null;
|
||||
this.adsManager = null;
|
||||
this.adsLoader = null;
|
||||
this.adsCuePoints = null;
|
||||
this.elements = {
|
||||
container: null,
|
||||
displayContainer: null,
|
||||
};
|
||||
this.manager = null;
|
||||
this.loader = null;
|
||||
this.cuePoints = null;
|
||||
this.events = {};
|
||||
this.safetyTimer = null;
|
||||
this.countdownTimer = null;
|
||||
|
||||
// Set listeners on the Plyr instance
|
||||
this.listeners();
|
||||
@ -54,21 +57,17 @@ class Ads {
|
||||
this.startSafetyTimer(12000, 'ready()');
|
||||
|
||||
// Setup a simple promise to resolve if the IMA loader is ready
|
||||
this.adsLoaderPromise = new Promise(resolve => {
|
||||
this.loaderPromise = new Promise(resolve => {
|
||||
this.on('ADS_LOADER_LOADED', () => resolve());
|
||||
});
|
||||
this.adsLoaderPromise.then(() => {
|
||||
this.player.debug.log('Ads loader resolved!', this.adsLoader);
|
||||
});
|
||||
|
||||
// Setup a promise to resolve if the IMA manager is ready
|
||||
this.adsManagerPromise = new Promise(resolve => {
|
||||
this.managerPromise = new Promise(resolve => {
|
||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||
});
|
||||
this.adsManagerPromise.then(() => {
|
||||
this.player.debug.log('Ads manager resolved!', this.adsManager);
|
||||
|
||||
// Clear the safety timer
|
||||
this.managerPromise.then(() => {
|
||||
this.clearSafetyTimer('onAdsManagerLoaded()');
|
||||
});
|
||||
|
||||
@ -86,23 +85,21 @@ class Ads {
|
||||
*/
|
||||
setupIMA() {
|
||||
// Create the container for our advertisements
|
||||
this.adsContainer = utils.createElement('div', {
|
||||
this.elements.container = utils.createElement('div', {
|
||||
class: this.player.config.classNames.ads,
|
||||
'data-badge-text': this.player.config.i18n.advertisement,
|
||||
hidden: '',
|
||||
});
|
||||
this.player.elements.container.appendChild(this.adsContainer);
|
||||
this.player.elements.container.appendChild(this.elements.container);
|
||||
|
||||
// So we can run VPAID2
|
||||
google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
|
||||
|
||||
// Set language
|
||||
// Todo: Could make a config option out of this locale value.
|
||||
google.ima.settings.setLocale('en');
|
||||
google.ima.settings.setLocale(this.player.config.ads.language);
|
||||
|
||||
// We assume the adContainer is the video container of the plyr element
|
||||
// that will house the ads
|
||||
this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer);
|
||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container);
|
||||
|
||||
// Request video ads to be pre-loaded
|
||||
this.requestAds();
|
||||
@ -116,27 +113,27 @@ class Ads {
|
||||
|
||||
try {
|
||||
// Create ads loader
|
||||
this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false);
|
||||
this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads
|
||||
const adsRequest = new google.ima.AdsRequest();
|
||||
adsRequest.adTagUrl = this.player.config.ads.tagUrl;
|
||||
const request = new google.ima.AdsRequest();
|
||||
request.adTagUrl = this.player.config.ads.tagUrl;
|
||||
|
||||
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||
// to select the correct creative if multiple are returned
|
||||
adsRequest.linearAdSlotWidth = container.offsetWidth;
|
||||
adsRequest.linearAdSlotHeight = container.offsetHeight;
|
||||
adsRequest.nonLinearAdSlotWidth = container.offsetWidth;
|
||||
adsRequest.nonLinearAdSlotHeight = container.offsetHeight;
|
||||
request.linearAdSlotWidth = container.offsetWidth;
|
||||
request.linearAdSlotHeight = container.offsetHeight;
|
||||
request.nonLinearAdSlotWidth = container.offsetWidth;
|
||||
request.nonLinearAdSlotHeight = container.offsetHeight;
|
||||
|
||||
// We only overlay ads as we only support video.
|
||||
adsRequest.forceNonLinearFullSlot = false;
|
||||
request.forceNonLinearFullSlot = false;
|
||||
|
||||
this.adsLoader.requestAds(adsRequest);
|
||||
this.loader.requestAds(request);
|
||||
|
||||
this.handleEventListeners('ADS_LOADER_LOADED');
|
||||
} catch (e) {
|
||||
@ -144,6 +141,25 @@ class Ads {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ad countdown
|
||||
* @param {boolean} start
|
||||
*/
|
||||
pollCountdown(start = false) {
|
||||
if (!start) {
|
||||
window.clearInterval(this.countdownTimer);
|
||||
this.elements.container.removeAttribute('data-badge-text');
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const time = utils.formatTime(this.manager.getRemainingTime());
|
||||
const text = this.player.config.i18n.adCountdown.replace('{countdown}', time);
|
||||
this.elements.container.setAttribute('data-badge-text', text);
|
||||
};
|
||||
|
||||
this.countdownTimer = window.setInterval(update, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
||||
* @param {Event} adsManagerLoadedEvent
|
||||
@ -158,33 +174,42 @@ class Ads {
|
||||
|
||||
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
||||
// so it can determine when to start the mid- and post-roll
|
||||
this.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
||||
this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
||||
|
||||
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||
this.adsCuePoints = this.adsManager.getCuePoints();
|
||||
this.cuePoints = this.manager.getCuePoints();
|
||||
|
||||
// Add advertisement cue's within the time line if available
|
||||
this.adsCuePoints.forEach(cuePoint => {
|
||||
this.cuePoints.forEach(cuePoint => {
|
||||
if (cuePoint !== 0 && cuePoint !== -1) {
|
||||
const seekElement = this.player.elements.progress;
|
||||
|
||||
if (seekElement) {
|
||||
const cuePercentage = 100 / this.player.duration * cuePoint;
|
||||
const cue = utils.createElement('span', {
|
||||
class: this.player.config.classNames.cues,
|
||||
});
|
||||
|
||||
cue.style.left = `${cuePercentage.toString()}%`;
|
||||
seekElement.appendChild(cue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get skippable state
|
||||
// TODO: Skip button
|
||||
// this.manager.getAdSkippableState();
|
||||
|
||||
// Set volume to match player
|
||||
this.manager.setVolume(this.player.volume);
|
||||
|
||||
// Add listeners to the required events
|
||||
// Advertisement error events
|
||||
this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
||||
this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
||||
|
||||
// Advertisement regular events
|
||||
Object.keys(google.ima.AdEvent.Type).forEach(type => {
|
||||
this.adsManager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
|
||||
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
|
||||
});
|
||||
|
||||
// Resolve our adsManager
|
||||
@ -194,25 +219,52 @@ class Ads {
|
||||
/**
|
||||
* This is where all the event handling takes place. Retrieve the ad from the event. Some
|
||||
* events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
|
||||
* https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
|
||||
* @param {Event} event
|
||||
*/
|
||||
onAdEvent(event) {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
// Listen for events if debugging
|
||||
this.player.debug.log(`Ads event: ${event.type}`);
|
||||
|
||||
// Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
|
||||
// don't have ad object associated
|
||||
const ad = event.getAd();
|
||||
|
||||
// Proxy event
|
||||
const dispatchEvent = type => {
|
||||
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
|
||||
};
|
||||
|
||||
switch (event.type) {
|
||||
case google.ima.AdEvent.Type.LOADED:
|
||||
// This is the first event sent for an ad - it is possible to determine whether the
|
||||
// ad is a video ad or an overlay
|
||||
this.handleEventListeners('LOADED');
|
||||
|
||||
// Bubble event
|
||||
dispatchEvent('loaded');
|
||||
|
||||
// Start countdown
|
||||
this.pollCountdown(true);
|
||||
|
||||
if (!ad.isLinear()) {
|
||||
// Position AdDisplayContainer correctly for overlay
|
||||
ad.width = container.offsetWidth;
|
||||
ad.height = container.offsetHeight;
|
||||
}
|
||||
|
||||
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
|
||||
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
||||
// All ads for the current videos are done. We can now request new advertisements
|
||||
// in case the video is re-played
|
||||
this.handleEventListeners('ALL_ADS_COMPLETED');
|
||||
|
||||
// Todo: Example for what happens when a next video in a playlist would be loaded.
|
||||
// Fire event
|
||||
dispatchEvent('allcomplete');
|
||||
|
||||
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
||||
// So here we load a new video when all ads are done.
|
||||
// Then we load new ads within a new adsManager. When the video
|
||||
// Is started - after - the ads are loaded, then we get ads.
|
||||
@ -232,7 +284,7 @@ class Ads {
|
||||
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
|
||||
// };
|
||||
|
||||
// Todo: So there is still this thing where a video should only be allowed to start
|
||||
// TODO: So there is still this thing where a video should only be allowed to start
|
||||
// playing when the IMA SDK is ready or has failed
|
||||
|
||||
this.loadAds();
|
||||
@ -243,7 +295,9 @@ class Ads {
|
||||
// for example display a pause button and remaining time. Fired when content should
|
||||
// be paused. This usually happens right before an ad is about to cover the content
|
||||
this.handleEventListeners('CONTENT_PAUSE_REQUESTED');
|
||||
dispatchEvent('contentpause');
|
||||
this.pauseContent();
|
||||
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
|
||||
@ -252,22 +306,31 @@ class Ads {
|
||||
// Fired when content should be resumed. This usually happens when an ad finishes
|
||||
// or collapses
|
||||
this.handleEventListeners('CONTENT_RESUME_REQUESTED');
|
||||
dispatchEvent('contentresume');
|
||||
this.resumeContent();
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.LOADED:
|
||||
// This is the first event sent for an ad - it is possible to determine whether the
|
||||
// ad is a video ad or an overlay
|
||||
this.handleEventListeners('LOADED');
|
||||
case google.ima.AdEvent.Type.STARTED:
|
||||
dispatchEvent('started');
|
||||
break;
|
||||
|
||||
if (!ad.isLinear()) {
|
||||
// Position AdDisplayContainer correctly for overlay
|
||||
ad.width = container.offsetWidth;
|
||||
ad.height = container.offsetHeight;
|
||||
}
|
||||
case google.ima.AdEvent.Type.MIDPOINT:
|
||||
dispatchEvent('midpoint');
|
||||
break;
|
||||
|
||||
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
|
||||
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
|
||||
case google.ima.AdEvent.Type.COMPLETE:
|
||||
dispatchEvent('complete');
|
||||
|
||||
// End countdown
|
||||
this.pollCountdown();
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.IMPRESSION:
|
||||
dispatchEvent('impression');
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.CLICK:
|
||||
dispatchEvent('click');
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -295,7 +358,7 @@ class Ads {
|
||||
|
||||
// Add listeners to the required events
|
||||
this.player.on('ended', () => {
|
||||
this.adsLoader.contentComplete();
|
||||
this.loader.contentComplete();
|
||||
});
|
||||
|
||||
this.player.on('seeking', () => {
|
||||
@ -306,17 +369,18 @@ class Ads {
|
||||
this.player.on('seeked', () => {
|
||||
const seekedTime = this.player.currentTime;
|
||||
|
||||
this.adsCuePoints.forEach((cuePoint, index) => {
|
||||
this.cuePoints.forEach((cuePoint, index) => {
|
||||
if (time < cuePoint && cuePoint < seekedTime) {
|
||||
this.adsManager.discardAdBreak();
|
||||
this.adsCuePoints.splice(index, 1);
|
||||
this.manager.discardAdBreak();
|
||||
this.cuePoints.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Listen to the resizing of the window. And resize ad accordingly
|
||||
// TODO: eventually implement ResizeObserver
|
||||
window.addEventListener('resize', () => {
|
||||
this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
});
|
||||
}
|
||||
|
||||
@ -326,23 +390,23 @@ class Ads {
|
||||
play() {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
if (!this.adsManagerPromise) {
|
||||
if (!this.managerPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Play the requested advertisement whenever the adsManager is ready
|
||||
this.adsManagerPromise.then(() => {
|
||||
this.managerPromise.then(() => {
|
||||
// Initialize the container. Must be done via a user action on mobile devices
|
||||
this.adDisplayContainer.initialize();
|
||||
this.elements.displayContainer.initialize();
|
||||
|
||||
try {
|
||||
if (!this.initialized) {
|
||||
// Initialize the ads manager. Ad rules playlist will start at this time
|
||||
this.adsManager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
|
||||
// Call play to start showing the ad. Single video and overlay ads will
|
||||
// start at this time; the call will be ignored for ad rules
|
||||
this.adsManager.start();
|
||||
this.manager.start();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
@ -358,10 +422,8 @@ class Ads {
|
||||
* Resume our video.
|
||||
*/
|
||||
resumeContent() {
|
||||
this.player.debug.log('Resume video');
|
||||
|
||||
// Hide our ad container
|
||||
utils.toggleHidden(this.adsContainer, true);
|
||||
utils.toggleHidden(this.elements.container, true);
|
||||
|
||||
// Ad is stopped
|
||||
this.playing = false;
|
||||
@ -376,10 +438,8 @@ class Ads {
|
||||
* Pause our video
|
||||
*/
|
||||
pauseContent() {
|
||||
this.player.debug.log('Pause video');
|
||||
|
||||
// Show our ad container.
|
||||
utils.toggleHidden(this.adsContainer, false);
|
||||
utils.toggleHidden(this.elements.container, false);
|
||||
|
||||
// Ad is playing.
|
||||
this.playing = true;
|
||||
@ -395,8 +455,6 @@ class Ads {
|
||||
* media-ads/docs/sdks/android/faq#8
|
||||
*/
|
||||
cancel() {
|
||||
this.player.debug.warn('Ad cancelled');
|
||||
|
||||
// Pause our video
|
||||
this.resumeContent();
|
||||
|
||||
@ -412,16 +470,16 @@ class Ads {
|
||||
*/
|
||||
loadAds() {
|
||||
// Tell our adsManager to go bye bye
|
||||
this.adsManagerPromise.then(() => {
|
||||
this.managerPromise.then(() => {
|
||||
// Destroy our adsManager
|
||||
if (this.adsManager) {
|
||||
this.adsManager.destroy();
|
||||
if (this.manager) {
|
||||
this.manager.destroy();
|
||||
}
|
||||
|
||||
// Re-set our adsManager promises
|
||||
this.adsManagerPromise = new Promise(resolve => {
|
||||
this.managerPromise = new Promise(resolve => {
|
||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||
this.player.debug.log(this.adsManager);
|
||||
this.player.debug.log(this.manager);
|
||||
});
|
||||
|
||||
// Make sure we can re-call advertisements
|
||||
@ -437,7 +495,7 @@ class Ads {
|
||||
* @param {string} event - Event type
|
||||
*/
|
||||
handleEventListeners(event) {
|
||||
if (typeof this.events[event] !== 'undefined') {
|
||||
if (utils.is.function(this.events[event])) {
|
||||
this.events[event].call(this);
|
||||
}
|
||||
}
|
||||
@ -458,11 +516,12 @@ class Ads {
|
||||
* The advertisement has 12 seconds to get its things together. We stop this timer when the
|
||||
* advertisement is playing, or when a user action is required to start, then we clear the
|
||||
* timer on ad ready
|
||||
* @param {Number} time
|
||||
* @param {String} from
|
||||
* @param {number} time
|
||||
* @param {string} from
|
||||
*/
|
||||
startSafetyTimer(time, from) {
|
||||
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||
|
||||
this.safetyTimer = window.setTimeout(() => {
|
||||
this.cancel();
|
||||
this.clearSafetyTimer('startSafetyTimer()');
|
||||
@ -471,13 +530,14 @@ class Ads {
|
||||
|
||||
/**
|
||||
* Clear our safety timer(s)
|
||||
* @param {String} from
|
||||
* @param {string} from
|
||||
*/
|
||||
clearSafetyTimer(from) {
|
||||
if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) {
|
||||
if (!utils.is.nullOrUndefined(this.safetyTimer)) {
|
||||
this.player.debug.log(`Safety timer cleared from: ${from}`);
|
||||
|
||||
clearTimeout(this.safetyTimer);
|
||||
this.safetyTimer = undefined;
|
||||
this.safetyTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
src/js/ui.js
26
src/js/ui.js
@ -293,33 +293,15 @@ const ui = {
|
||||
// Update the displayed time
|
||||
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
||||
// Bail if there's no element to display or the value isn't a number
|
||||
if (!utils.is.element(target) || !utils.is.number(time)) {
|
||||
if (!utils.is.element(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format time component to add leading zero
|
||||
const format = value => `0${value}`.slice(-2);
|
||||
// Always display hours if duration is over an hour
|
||||
const displayHours = utils.getHours(this.duration) > 0;
|
||||
|
||||
// Helpers
|
||||
const getHours = value => parseInt((value / 60 / 60) % 60, 10);
|
||||
const getMinutes = value => parseInt((value / 60) % 60, 10);
|
||||
const getSeconds = value => parseInt(value % 60, 10);
|
||||
|
||||
// Breakdown to hours, mins, secs
|
||||
let hours = getHours(time);
|
||||
const mins = getMinutes(time);
|
||||
const secs = getSeconds(time);
|
||||
|
||||
// Do we need to display hours?
|
||||
if (getHours(this.duration) > 0) {
|
||||
hours = `${hours}:`;
|
||||
} else {
|
||||
hours = '';
|
||||
}
|
||||
|
||||
// Render
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
target.textContent = `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||
target.textContent = utils.formatTime(time, displayHours, inverted);
|
||||
},
|
||||
|
||||
// Handle time change event
|
||||
|
@ -605,6 +605,32 @@ const utils = {
|
||||
return (current / max * 100).toFixed(2);
|
||||
},
|
||||
|
||||
// Time helpers
|
||||
getHours(value) { return parseInt((value / 60 / 60) % 60, 10); },
|
||||
getMinutes(value) { return parseInt((value / 60) % 60, 10); },
|
||||
getSeconds(value) { return parseInt(value % 60, 10); },
|
||||
|
||||
// Format time to UI friendly string
|
||||
formatTime(time = 0, displayHours = false, inverted = false) {
|
||||
// Format time component to add leading zero
|
||||
const format = value => `0${value}`.slice(-2);
|
||||
|
||||
// Breakdown to hours, mins, secs
|
||||
let hours = this.getHours(time);
|
||||
const mins = this.getMinutes(time);
|
||||
const secs = this.getSeconds(time);
|
||||
|
||||
// Do we need to display hours?
|
||||
if (displayHours || hours > 0) {
|
||||
hours = `${hours}:`;
|
||||
} else {
|
||||
hours = '';
|
||||
}
|
||||
|
||||
// Render
|
||||
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||
},
|
||||
|
||||
// Deep extend destination object with N more objects
|
||||
extend(target = {}, ...sources) {
|
||||
if (!sources.length) {
|
||||
|
@ -14,16 +14,20 @@
|
||||
&::after {
|
||||
background: rgba($plyr-color-gunmetal, 0.8);
|
||||
border-radius: 2px;
|
||||
bottom: ($plyr-control-spacing * 2);
|
||||
bottom: $plyr-control-spacing;
|
||||
color: #fff;
|
||||
content: attr(data-badge-text);
|
||||
font-size: $plyr-font-size-captions-small;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: ($plyr-control-spacing * 2);
|
||||
right: $plyr-control-spacing;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&::after:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Advertisement cue's for the progress bar.
|
||||
|
Loading…
x
Reference in New Issue
Block a user