commit
3aa5747c90
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,3 +7,5 @@ index-*.html
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
*.webm
|
*.webm
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
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 @@
|
|||||||
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"}});window.player=t;var o=document.querySelectorAll("[data-source]"),i={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),n=window.history&&window.history.pushState;function a(e,t,o){e&&e.classList[o?"add":"remove"](t)}function s(e,n){if(e in i&&(n||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 a(e.parentElement,"active",!1)}),a(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),n&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&s(e.state.type)}),n){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&&(!function(e,t,o,i,r,n,a){e.GoogleAnalyticsObject="ga",e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,n=t.createElement("script"),a=t.getElementsByTagName("script")[0],n.async=1,n.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(n,a)}(window,document),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
|
//# 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
@ -51,6 +51,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
keys: {
|
keys: {
|
||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
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',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expose for testing
|
// Expose for testing
|
||||||
|
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
@ -195,7 +195,7 @@ build.sprite('plyr');
|
|||||||
|
|
||||||
// Demo files
|
// Demo files
|
||||||
build.sass(bundles.demo.sass, 'demo');
|
build.sass(bundles.demo.sass, 'demo');
|
||||||
build.js(bundles.demo.js, 'demo', { format: 'es' });
|
build.js(bundles.demo.js, 'demo', { format: 'iife' });
|
||||||
|
|
||||||
// Build all JS
|
// Build all JS
|
||||||
gulp.task('js', () => {
|
gulp.task('js', () => {
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
// Default config
|
// ==========================================================================
|
||||||
|
// Plyr default config
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
// Disable
|
// Disable
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -176,6 +179,7 @@ const defaults = {
|
|||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
none: 'None',
|
none: 'None',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
|
adCountdown: 'Ad - {countdown}',
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -186,6 +190,9 @@ const defaults = {
|
|||||||
youtube: {
|
youtube: {
|
||||||
api: 'https://www.youtube.com/iframe_api',
|
api: 'https://www.youtube.com/iframe_api',
|
||||||
},
|
},
|
||||||
|
googleIMA: {
|
||||||
|
api: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Custom control listeners
|
// Custom control listeners
|
||||||
@ -247,6 +254,17 @@ const defaults = {
|
|||||||
'statechange',
|
'statechange',
|
||||||
'qualitychange',
|
'qualitychange',
|
||||||
'qualityrequested',
|
'qualityrequested',
|
||||||
|
|
||||||
|
// Ads
|
||||||
|
'adsloaded',
|
||||||
|
'adscontentpause',
|
||||||
|
'adsconentresume',
|
||||||
|
'adstarted',
|
||||||
|
'adsmidpoint',
|
||||||
|
'adscomplete',
|
||||||
|
'adsallcomplete',
|
||||||
|
'adsimpression',
|
||||||
|
'adsclick',
|
||||||
],
|
],
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
@ -299,6 +317,7 @@ const defaults = {
|
|||||||
classNames: {
|
classNames: {
|
||||||
video: 'plyr__video-wrapper',
|
video: 'plyr__video-wrapper',
|
||||||
embed: 'plyr__video-embed',
|
embed: 'plyr__video-embed',
|
||||||
|
ads: 'plyr__ads',
|
||||||
control: 'plyr__control',
|
control: 'plyr__control',
|
||||||
type: 'plyr--{0}',
|
type: 'plyr--{0}',
|
||||||
provider: 'plyr--{0}',
|
provider: 'plyr--{0}',
|
||||||
@ -308,6 +327,7 @@ const defaults = {
|
|||||||
error: 'plyr--has-error',
|
error: 'plyr--has-error',
|
||||||
hover: 'plyr--hover',
|
hover: 'plyr--hover',
|
||||||
tooltip: 'plyr__tooltip',
|
tooltip: 'plyr__tooltip',
|
||||||
|
cues: 'plyr__cues',
|
||||||
hidden: 'plyr__sr-only',
|
hidden: 'plyr__sr-only',
|
||||||
hideControls: 'plyr--hide-controls',
|
hideControls: 'plyr--hide-controls',
|
||||||
isIos: 'plyr--is-ios',
|
isIos: 'plyr--is-ios',
|
||||||
@ -342,6 +362,11 @@ const defaults = {
|
|||||||
keys: {
|
keys: {
|
||||||
google: null,
|
google: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Ads
|
||||||
|
ads: {
|
||||||
|
tagUrl: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaults;
|
export default defaults;
|
||||||
|
545
src/js/plugins/ads.js
Normal file
545
src/js/plugins/ads.js
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Advertisement plugin using Google IMA HTML5 SDK
|
||||||
|
// Create an account with our ad partner, vi here:
|
||||||
|
// https://www.vi.ai/publisher-video-monetization/
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
/* global google */
|
||||||
|
|
||||||
|
import utils from '../utils';
|
||||||
|
|
||||||
|
class Ads {
|
||||||
|
/**
|
||||||
|
* Ads constructor.
|
||||||
|
* @param {object} player
|
||||||
|
* @return {Ads}
|
||||||
|
*/
|
||||||
|
constructor(player) {
|
||||||
|
this.player = player;
|
||||||
|
this.playing = false;
|
||||||
|
this.initialized = false;
|
||||||
|
|
||||||
|
// Check if a tag URL is provided.
|
||||||
|
if (!utils.is.url(player.config.ads.tagUrl)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the Google IMA3 SDK is loaded
|
||||||
|
if (!utils.is.object(window.google)) {
|
||||||
|
utils.loadScript(player.config.urls.googleIMA.api, () => {
|
||||||
|
this.ready();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ads instance ready.
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Start ticking our safety timer. If the whole advertisement
|
||||||
|
// thing doesn't resolve within our set time; we bail
|
||||||
|
this.startSafetyTimer(12000, 'ready()');
|
||||||
|
|
||||||
|
// Setup a simple promise to resolve if the IMA loader is ready
|
||||||
|
this.loaderPromise = new Promise(resolve => {
|
||||||
|
this.on('ADS_LOADER_LOADED', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup a promise to resolve if the IMA manager is ready
|
||||||
|
this.managerPromise = new Promise(resolve => {
|
||||||
|
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear the safety timer
|
||||||
|
this.managerPromise.then(() => {
|
||||||
|
this.clearSafetyTimer('onAdsManagerLoaded()');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the IMA SDK
|
||||||
|
this.setupIMA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
||||||
|
* so here we define our ad container. This div is set up to render on top of the video player.
|
||||||
|
* Using the code below, we tell the SDK to render ads within that div. We also provide a
|
||||||
|
* handle to the content video player - the SDK will poll the current time of our player to
|
||||||
|
* properly place mid-rolls. After we create the ad display container, we initialize it. On
|
||||||
|
* mobile devices, this initialization is done as the result of a user action.
|
||||||
|
*/
|
||||||
|
setupIMA() {
|
||||||
|
// Create the container for our advertisements
|
||||||
|
this.elements.container = utils.createElement('div', {
|
||||||
|
class: this.player.config.classNames.ads,
|
||||||
|
hidden: '',
|
||||||
|
});
|
||||||
|
this.player.elements.container.appendChild(this.elements.container);
|
||||||
|
|
||||||
|
// So we can run VPAID2
|
||||||
|
google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
|
||||||
|
|
||||||
|
// Set language
|
||||||
|
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.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container);
|
||||||
|
|
||||||
|
// Request video ads to be pre-loaded
|
||||||
|
this.requestAds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request advertisements
|
||||||
|
*/
|
||||||
|
requestAds() {
|
||||||
|
const { container } = this.player.elements;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create ads loader
|
||||||
|
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||||
|
|
||||||
|
// Listen and respond to ads loaded and error events
|
||||||
|
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 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
|
||||||
|
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.
|
||||||
|
request.forceNonLinearFullSlot = false;
|
||||||
|
|
||||||
|
this.loader.requestAds(request);
|
||||||
|
|
||||||
|
this.handleEventListeners('ADS_LOADER_LOADED');
|
||||||
|
} catch (e) {
|
||||||
|
this.onAdError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
onAdsManagerLoaded(adsManagerLoadedEvent) {
|
||||||
|
// Get the ads manager
|
||||||
|
const settings = new google.ima.AdsRenderingSettings();
|
||||||
|
|
||||||
|
// Tell the SDK to save and restore content video state on our behalf
|
||||||
|
settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
|
||||||
|
settings.enablePreloading = true;
|
||||||
|
|
||||||
|
// 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.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
||||||
|
|
||||||
|
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||||
|
this.cuePoints = this.manager.getCuePoints();
|
||||||
|
|
||||||
|
// Add advertisement cue's within the time line if available
|
||||||
|
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.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
||||||
|
|
||||||
|
// Advertisement regular events
|
||||||
|
Object.keys(google.ima.AdEvent.Type).forEach(type => {
|
||||||
|
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve our adsManager
|
||||||
|
this.handleEventListeners('ADS_MANAGER_LOADED');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// You can also easily test cancelling and reloading by running
|
||||||
|
// player.ads.cancel() and player.ads.play from the console I guess.
|
||||||
|
// this.player.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: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
|
||||||
|
// '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
|
||||||
|
// playing when the IMA SDK is ready or has failed
|
||||||
|
|
||||||
|
this.loadAds();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
|
// This event indicates the ad has started - the video player can adjust the UI,
|
||||||
|
// 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:
|
||||||
|
// This event indicates the ad has finished - the video player can perform
|
||||||
|
// appropriate UI actions, such as removing the timer for remaining time detection.
|
||||||
|
// 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.STARTED:
|
||||||
|
dispatchEvent('started');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case google.ima.AdEvent.Type.MIDPOINT:
|
||||||
|
dispatchEvent('midpoint');
|
||||||
|
break;
|
||||||
|
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any ad error handling comes through here
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
onAdError(event) {
|
||||||
|
this.cancel();
|
||||||
|
this.player.debug.log('Ads error', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup hooks for Plyr and window events. This ensures
|
||||||
|
* the mid- and post-roll launch at the correct time. And
|
||||||
|
* resize the advertisement when the player resizes
|
||||||
|
*/
|
||||||
|
listeners() {
|
||||||
|
const { container } = this.player.elements;
|
||||||
|
let time;
|
||||||
|
|
||||||
|
// Add listeners to the required events
|
||||||
|
this.player.on('ended', () => {
|
||||||
|
this.loader.contentComplete();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.player.on('seeking', () => {
|
||||||
|
time = this.player.currentTime;
|
||||||
|
return time;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.player.on('seeked', () => {
|
||||||
|
const seekedTime = this.player.currentTime;
|
||||||
|
|
||||||
|
this.cuePoints.forEach((cuePoint, index) => {
|
||||||
|
if (time < cuePoint && cuePoint < seekedTime) {
|
||||||
|
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.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the adsManager and start playing advertisements
|
||||||
|
*/
|
||||||
|
play() {
|
||||||
|
const { container } = this.player.elements;
|
||||||
|
|
||||||
|
if (!this.managerPromise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the requested advertisement whenever the adsManager is ready
|
||||||
|
this.managerPromise.then(() => {
|
||||||
|
// Initialize the container. Must be done via a user action on mobile devices
|
||||||
|
this.elements.displayContainer.initialize();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.initialized) {
|
||||||
|
// Initialize the ads manager. Ad rules playlist will start at this time
|
||||||
|
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.manager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
} catch (adError) {
|
||||||
|
// An error may be thrown if there was a problem with the
|
||||||
|
// VAST response
|
||||||
|
this.onAdError(adError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume our video.
|
||||||
|
*/
|
||||||
|
resumeContent() {
|
||||||
|
// Hide our ad container
|
||||||
|
utils.toggleHidden(this.elements.container, true);
|
||||||
|
|
||||||
|
// Ad is stopped
|
||||||
|
this.playing = false;
|
||||||
|
|
||||||
|
// Play our video
|
||||||
|
if (this.player.currentTime < this.player.duration) {
|
||||||
|
this.player.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause our video
|
||||||
|
*/
|
||||||
|
pauseContent() {
|
||||||
|
// Show our ad container.
|
||||||
|
utils.toggleHidden(this.elements.container, false);
|
||||||
|
|
||||||
|
// Ad is playing.
|
||||||
|
this.playing = true;
|
||||||
|
|
||||||
|
// Pause our video.
|
||||||
|
this.player.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the adsManager so we can grab new ads after this. If we don't then we're not
|
||||||
|
* allowed to call new ads based on google policies, as they interpret this as an accidental
|
||||||
|
* video requests. https://developers.google.com/interactive-
|
||||||
|
* media-ads/docs/sdks/android/faq#8
|
||||||
|
*/
|
||||||
|
cancel() {
|
||||||
|
// Pause our video
|
||||||
|
this.resumeContent();
|
||||||
|
|
||||||
|
// Tell our instance that we're done for now
|
||||||
|
this.handleEventListeners('ERROR');
|
||||||
|
|
||||||
|
// Re-create our adsManager
|
||||||
|
this.loadAds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-create our adsManager
|
||||||
|
*/
|
||||||
|
loadAds() {
|
||||||
|
// Tell our adsManager to go bye bye
|
||||||
|
this.managerPromise.then(() => {
|
||||||
|
// Destroy our adsManager
|
||||||
|
if (this.manager) {
|
||||||
|
this.manager.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-set our adsManager promises
|
||||||
|
this.managerPromise = new Promise(resolve => {
|
||||||
|
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||||
|
this.player.debug.log(this.manager);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure we can re-call advertisements
|
||||||
|
this.initialized = false;
|
||||||
|
|
||||||
|
// Now request some new advertisements
|
||||||
|
this.requestAds();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles callbacks after an ad event was invoked
|
||||||
|
* @param {string} event - Event type
|
||||||
|
*/
|
||||||
|
handleEventListeners(event) {
|
||||||
|
if (utils.is.function(this.events[event])) {
|
||||||
|
this.events[event].call(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add event listeners
|
||||||
|
* @param {string} event - Event type
|
||||||
|
* @param {function} callback - Callback for when event occurs
|
||||||
|
* @return {Ads}
|
||||||
|
*/
|
||||||
|
on(event, callback) {
|
||||||
|
this.events[event] = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a safety timer for when the ad network doesn't respond for whatever reason.
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
startSafetyTimer(time, from) {
|
||||||
|
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||||
|
|
||||||
|
this.safetyTimer = window.setTimeout(() => {
|
||||||
|
this.cancel();
|
||||||
|
this.clearSafetyTimer('startSafetyTimer()');
|
||||||
|
}, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear our safety timer(s)
|
||||||
|
* @param {string} from
|
||||||
|
*/
|
||||||
|
clearSafetyTimer(from) {
|
||||||
|
if (!utils.is.nullOrUndefined(this.safetyTimer)) {
|
||||||
|
this.player.debug.log(`Safety timer cleared from: ${from}`);
|
||||||
|
|
||||||
|
clearTimeout(this.safetyTimer);
|
||||||
|
this.safetyTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ads;
|
@ -12,6 +12,7 @@ import utils from './utils';
|
|||||||
|
|
||||||
import Console from './console';
|
import Console from './console';
|
||||||
import Storage from './storage';
|
import Storage from './storage';
|
||||||
|
import Ads from './plugins/ads';
|
||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
@ -273,6 +274,11 @@ class Plyr {
|
|||||||
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup ads if provided
|
||||||
|
if (utils.is.url(this.config.ads.tagUrl)) {
|
||||||
|
this.ads = new Ads(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@ -302,10 +308,21 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the media
|
* Play the media, or play the advertisement
|
||||||
*/
|
*/
|
||||||
play() {
|
play() {
|
||||||
return this.media.play();
|
if (utils.is.url(this.config.ads.tagUrl)) {
|
||||||
|
if (this.ads.playing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.ads.initialized) {
|
||||||
|
this.ads.play();
|
||||||
|
}
|
||||||
|
if (!this.ads.playing) {
|
||||||
|
this.media.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.media.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
26
src/js/ui.js
26
src/js/ui.js
@ -293,33 +293,15 @@ const ui = {
|
|||||||
// Update the displayed time
|
// Update the displayed time
|
||||||
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
||||||
// Bail if there's no element to display or the value isn't a number
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format time component to add leading zero
|
// Always display hours if duration is over an hour
|
||||||
const format = value => `0${value}`.slice(-2);
|
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
|
// 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
|
// Handle time change event
|
||||||
|
@ -50,6 +50,9 @@ const utils = {
|
|||||||
track(input) {
|
track(input) {
|
||||||
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
|
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
|
||||||
},
|
},
|
||||||
|
url(input) {
|
||||||
|
return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
|
||||||
|
},
|
||||||
nullOrUndefined(input) {
|
nullOrUndefined(input) {
|
||||||
return input === null || typeof input === 'undefined';
|
return input === null || typeof input === 'undefined';
|
||||||
},
|
},
|
||||||
@ -602,6 +605,32 @@ const utils = {
|
|||||||
return (current / max * 100).toFixed(2);
|
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
|
// Deep extend destination object with N more objects
|
||||||
extend(target = {}, ...sources) {
|
extend(target = {}, ...sources) {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
@ -746,9 +775,9 @@ const utils = {
|
|||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
repaint(element) {
|
repaint(element) {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
element.setAttribute('hidden', '');
|
utils.toggleHidden(element, true);
|
||||||
element.offsetHeight; // eslint-disable-line
|
element.offsetHeight; // eslint-disable-line
|
||||||
element.removeAttribute('hidden');
|
utils.toggleHidden(element, false);
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
45
src/sass/plugins/ads.scss
Normal file
45
src/sass/plugins/ads.scss
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Advertisments
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
.plyr__ads {
|
||||||
|
bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 3; // Above the controls.
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: rgba($plyr-color-gunmetal, 0.8);
|
||||||
|
border-radius: 2px;
|
||||||
|
bottom: $plyr-control-spacing;
|
||||||
|
color: #fff;
|
||||||
|
content: attr(data-badge-text);
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
right: $plyr-control-spacing;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advertisement cue's for the progress bar.
|
||||||
|
.plyr__cues {
|
||||||
|
background: currentColor;
|
||||||
|
display: block;
|
||||||
|
height: $plyr-range-track-height;
|
||||||
|
left: 0;
|
||||||
|
margin: -($plyr-range-track-height / 2) 0 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: 3px;
|
||||||
|
z-index: 3; // Between progress and thumb.
|
||||||
|
}
|
@ -40,5 +40,7 @@
|
|||||||
@import 'states/error';
|
@import 'states/error';
|
||||||
@import 'states/fullscreen';
|
@import 'states/fullscreen';
|
||||||
|
|
||||||
|
@import 'plugins/ads';
|
||||||
|
|
||||||
@import 'utils/animation';
|
@import 'utils/animation';
|
||||||
@import 'utils/hidden';
|
@import 'utils/hidden';
|
||||||
|
35
yarn.lock
35
yarn.lock
@ -887,12 +887,6 @@ braces@^2.3.0:
|
|||||||
split-string "^3.0.2"
|
split-string "^3.0.2"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
browser-resolve@^1.11.0:
|
|
||||||
version "1.11.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
|
|
||||||
dependencies:
|
|
||||||
resolve "1.1.7"
|
|
||||||
|
|
||||||
browserslist@^2.1.2, browserslist@^2.10.0:
|
browserslist@^2.1.2, browserslist@^2.10.0:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.10.0.tgz#bac5ee1cc69ca9d96403ffb8a3abdc5b6aed6346"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.10.0.tgz#bac5ee1cc69ca9d96403ffb8a3abdc5b6aed6346"
|
||||||
@ -1176,6 +1170,10 @@ commander@^2.9.0, commander@~2.12.1:
|
|||||||
version "2.12.2"
|
version "2.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
|
||||||
|
|
||||||
|
commander@~2.13.0:
|
||||||
|
version "2.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
|
||||||
|
|
||||||
component-emitter@^1.2.1:
|
component-emitter@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
|
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
|
||||||
@ -4529,10 +4527,6 @@ resolve-url@^0.2.1, resolve-url@~0.2.1:
|
|||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||||
|
|
||||||
resolve@1.1.7:
|
|
||||||
version "1.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
|
||||||
|
|
||||||
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.4.0:
|
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.4.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
|
||||||
@ -4568,11 +4562,10 @@ rollup-plugin-commonjs@^8.2.6:
|
|||||||
resolve "^1.4.0"
|
resolve "^1.4.0"
|
||||||
rollup-pluginutils "^2.0.1"
|
rollup-pluginutils "^2.0.1"
|
||||||
|
|
||||||
rollup-plugin-node-resolve@^3.0.0:
|
rollup-plugin-node-resolve@^3.0.2:
|
||||||
version "3.0.0"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.0.0.tgz#8b897c4c3030d5001277b0514b25d2ca09683ee0"
|
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.0.2.tgz#38babc12fd404cc2ba1ff68648fe43fa3ffee6b0"
|
||||||
dependencies:
|
dependencies:
|
||||||
browser-resolve "^1.11.0"
|
|
||||||
builtin-modules "^1.1.0"
|
builtin-modules "^1.1.0"
|
||||||
is-module "^1.0.0"
|
is-module "^1.0.0"
|
||||||
resolve "^1.1.6"
|
resolve "^1.1.6"
|
||||||
@ -4999,9 +4992,9 @@ stylelint-config-recommended@^2.0.0:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.0.1.tgz#4746119ec85f5f4663c7b5107c05c13ed0e2ab0d"
|
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.0.1.tgz#4746119ec85f5f4663c7b5107c05c13ed0e2ab0d"
|
||||||
|
|
||||||
stylelint-config-sass-guidelines@^4.0.1:
|
stylelint-config-sass-guidelines@^4.1.0:
|
||||||
version "4.0.1"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-4.0.1.tgz#c64f353e07575ad06f6405d6dd902066aa5bc8c4"
|
resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-4.1.0.tgz#08d1d9a597704fd9945eb85422abb24f8608a170"
|
||||||
|
|
||||||
stylelint-config-standard@^18.0.0:
|
stylelint-config-standard@^18.0.0:
|
||||||
version "18.0.0"
|
version "18.0.0"
|
||||||
@ -5286,11 +5279,11 @@ typedarray@^0.0.6:
|
|||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
|
||||||
uglify-es@^3.3.5:
|
uglify-es@^3.3.7:
|
||||||
version "3.3.5"
|
version "3.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.5.tgz#cf7e695da81999f85196b15e2978862f13212f88"
|
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.7.tgz#d1249af668666aba7cb1163e277455be9eb393cf"
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "~2.12.1"
|
commander "~2.13.0"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
uglify-js@^3.0.9:
|
uglify-js@^3.0.9:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user