Compare commits

...

20 Commits

Author SHA1 Message Date
c74b75e8e1 3.0.0-beta.20 2018-03-13 23:35:17 +11:00
0538476d6f 3.0.0-beta.19 2018-03-13 22:15:28 +11:00
5ebe18d081 Typography fix 2018-03-13 22:00:40 +11:00
207adde36d 3.0.0-beta.18 2018-03-13 21:44:18 +11:00
1b13ddaa54 Update ads 2018-03-13 21:42:01 +11:00
9981c349be Fix for null manager race condition 2018-03-11 18:23:47 +11:00
b3365a7373 Normalised event names and removed unused 2018-03-11 12:54:51 +11:00
9a0c1c830d Merge pull request #804 from friday/ads-trigger-arguments
[v3] Add optional arguments to Ads.trigger
2018-03-11 10:55:29 +11:00
ef27ba16f4 Add optional argument to Ads.trigger (currently only used for adblocker error) 2018-03-10 16:20:33 +01:00
e206edc1f6 Event listener fixes, loadScript promise, ads tweaks 2018-03-11 02:03:35 +11:00
c734bc4957 Merge branch 'beta' of github.com:sampotts/plyr into beta 2018-03-10 23:32:55 +11:00
572b8a7aca Manually merged PRs 2018-03-10 23:32:48 +11:00
eebae4a227 Merge pull request #802 from gehaktmolen/ad-hotfixes
Advertisement couldnt be loaded when creative dimensions do not align after resizing
2018-03-10 23:32:15 +11:00
6a2ca534d2 Removed redundant wrappers within the adsmanager promises. 2018-03-09 14:29:37 +01:00
7adc2bc6c8 Unneeded else has been removed within the play() method. 2018-03-09 13:21:19 +01:00
ba8d7831a7 Made sure play() returns a promise. 2018-03-09 12:50:57 +01:00
69ffcbad27 Ad block detection would not work when calling play() right after creating the player instance, so the adsManager now also rejects on such a case. Also made sure that calling play() will wait for the adsManager promise to resolve or otherwise return the media.play() method. 2018-03-09 11:17:24 +01:00
819f7d1080 Resizing the ad container while having it on display none will return offset width and height of 0, which will cause ads not to play when ad sizes are set within the clients DSP. Also making sure that the inner containers of the ad container are full size. The container is now hidden/ displayed using z-index. 2018-03-07 15:43:48 +01:00
409b588458 Made sure that cue points for midrolls are not displayed when the ad rule for a midroll doesn't exceed the total play time of a video. 2018-03-07 15:17:30 +01:00
e90a603d57 Removed a double this.enabled variable and updated a comment in ads.js. Also made sure the adsmanager promise also can fail, so we can use it to wait for getting the advertisement ready when someone clicks the play button. Otherwise there it can look glitchy when the actual video starts playing and the video ad plays a few seconds later because the vast tag was slow to retrieve. Also fixed a typo. 2018-03-06 17:27:59 +01:00
32 changed files with 3820 additions and 3543 deletions

View File

@ -1,6 +1,6 @@
{
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
"extends": ["stylelint-config-sass-guidelines", "stylelint-config-recommended", "stylelint-config-prettier"],
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
"rules": {
"selector-class-pattern": null,
"selector-no-qualifying-type": [

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

4
demo/dist/demo.js vendored
View File

@ -41,7 +41,7 @@ document.addEventListener('DOMContentLoaded', function () {
var player = new Plyr('#player', {
debug: true,
title: 'View From A Blue Moon',
// iconUrl: '../dist/plyr.svg',
iconUrl: '../dist/plyr.svg',
keyboard: {
global: true
},
@ -231,7 +231,7 @@ if (window.location.host === 'plyr.io') {
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
!function(){"use strict";var e,t,o,i,r,a;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&&setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new Plyr("#player",{debug:!0,title:"View From A Blue Moon",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{enabled:!0}});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,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,r,a;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&&setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new 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:{enabled:!0}});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,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="https://www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}();
//# sourceMappingURL=demo.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -66,7 +66,7 @@
</p>
<p>Premium video monitization from
<a href="https://vi.ai/publisher-video-monetization/" target="_blank" class="no-border">
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
<span class="sr-only">ai.vi</span>
</a>

View File

@ -38,7 +38,7 @@ document.addEventListener('DOMContentLoaded', () => {
const player = new Plyr('#player', {
debug: true,
title: 'View From A Blue Moon',
// iconUrl: '../dist/plyr.svg',
iconUrl: '../dist/plyr.svg',
keyboard: {
global: true,
},
@ -238,7 +238,7 @@ if (window.location.host === 'plyr.io') {
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

3074
dist/plyr.js vendored

File diff suppressed because it is too large Load Diff

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3076
dist/plyr.polyfilled.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.0.0-beta.17",
"version": "3.0.0-beta.20",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"main": "./dist/plyr.js",
@ -17,11 +17,11 @@
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.9.0",
"git-branch": "^1.0.0",
"git-branch": "^2.0.1",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^5.0.0",
"gulp-better-rollup": "^3.0.0",
"gulp-clean-css": "^3.9.2",
"gulp-clean-css": "^3.9.3",
"gulp-concat": "^2.6.1",
"gulp-filter": "^5.1.0",
"gulp-open": "^3.0.0",
@ -36,15 +36,15 @@
"gulp-uglify-es": "^1.0.1",
"gulp-util": "^3.0.8",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.3.0",
"rollup-plugin-node-resolve": "^3.0.3",
"rollup-plugin-commonjs": "^8.4.1",
"rollup-plugin-node-resolve": "^3.2.0",
"run-sequence": "^2.2.1",
"stylelint": "^9.1.1",
"stylelint-config-prettier": "^2.1.0",
"stylelint-config-sass-guidelines": "^5.0.0",
"stylelint-config-prettier": "^3.0.4",
"stylelint-config-recommended": "^2.1.0",
"stylelint-config-sass-guidelines": "^5.0.0",
"stylelint-order": "^0.8.1",
"stylelint-scss": "^2.4.0",
"stylelint-scss": "^2.5.0",
"stylelint-selector-bem-pattern": "^2.0.0"
},
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],

View File

@ -128,7 +128,7 @@ See [initialising](#initialising) for more information on advanced setups.
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html
<script src="https://cdn.plyr.io/3.0.0-beta.17/plyr.js"></script>
<script src="https://cdn.plyr.io/3.0.0-beta.20/plyr.js"></script>
```
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
@ -144,13 +144,13 @@ Include the `plyr.css` stylsheet into your `<head>`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.0-beta.17/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.0-beta.20/plyr.css">
```
### SVG Sprite
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.17/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.20/plyr.svg`.
## Advanced
@ -278,7 +278,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
| `fullscreen` | Object | `{ enabled: true, fallback: true }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. |
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |

59
src/js/controls.js vendored
View File

@ -50,7 +50,7 @@ const controls = {
icon,
utils.extend(attributes, {
role: 'presentation',
})
}),
);
// Create the <use> to reference sprite
@ -115,8 +115,8 @@ const controls = {
{
class: this.config.classNames.menu.badge,
},
text
)
text,
),
);
return badge;
@ -238,7 +238,7 @@ const controls = {
for: attributes.id,
class: this.config.classNames.hidden,
},
this.config.i18n[type]
this.config.i18n[type],
);
// Seek input
@ -254,8 +254,8 @@ const controls = {
value: 0,
autocomplete: 'off',
},
attributes
)
attributes,
),
);
this.elements.inputs[type] = input;
@ -280,8 +280,8 @@ const controls = {
max: 100,
value: 0,
},
attributes
)
attributes,
),
);
// Create the label inside
@ -322,8 +322,8 @@ const controls = {
{
class: this.config.classNames.hidden,
},
this.config.i18n[type]
)
this.config.i18n[type],
),
);
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
@ -349,7 +349,7 @@ const controls = {
value,
checked,
class: 'plyr__sr-only',
})
}),
);
const faux = utils.createElement('span', { 'aria-hidden': true });
@ -427,6 +427,11 @@ const controls = {
// Set the YouTube quality menu
// TODO: Support for HTML5
setQualityMenu(options) {
// Menu required
if (!utils.is.element(this.elements.settings.panes.quality)) {
return;
}
const type = 'quality';
const list = this.elements.settings.panes.quality.querySelector('ul');
@ -482,7 +487,7 @@ const controls = {
};
this.options.quality.forEach(quality =>
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality))
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality)),
);
controls.updateSetting.call(this, type, list);
@ -583,6 +588,11 @@ const controls = {
// Set the looping options
/* setLoopMenu() {
// Menu required
if (!utils.is.element(this.elements.settings.panes.loop)) {
return;
}
const options = ['start', 'end', 'all', 'reset'];
const list = this.elements.settings.panes.loop.querySelector('ul');
@ -681,7 +691,7 @@ const controls = {
'language',
track.label || track.language,
controls.createBadge.call(this, track.language.toUpperCase()),
track.language.toLowerCase() === this.captions.language.toLowerCase()
track.language.toLowerCase() === this.captions.language.toLowerCase(),
);
});
@ -690,6 +700,11 @@ const controls = {
// Set a list of available captions languages
setSpeedMenu() {
// Menu required
if (!utils.is.element(this.elements.settings.panes.speed)) {
return;
}
const type = 'speed';
// Set the default speeds
@ -737,6 +752,12 @@ const controls = {
toggleMenu(event) {
const { form } = this.elements.settings;
const button = this.elements.buttons.settings;
// Menu and button are required
if (!utils.is.element(form) || !utils.is.element(button)) {
return;
}
const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true';
if (utils.is.event(event)) {
@ -933,7 +954,7 @@ const controls = {
role: 'tooltip',
class: this.config.classNames.tooltip,
},
'00:00'
'00:00',
);
progress.appendChild(tooltip);
@ -978,7 +999,7 @@ const controls = {
'volume',
utils.extend(attributes, {
id: `plyr-volume-${data.id}`,
})
}),
);
volume.appendChild(range.label);
volume.appendChild(range.input);
@ -1005,7 +1026,7 @@ const controls = {
'aria-haspopup': true,
'aria-controls': `plyr-settings-${data.id}`,
'aria-expanded': false,
})
}),
);
const form = utils.createElement('form', {
@ -1048,7 +1069,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-${type}`,
'aria-expanded': false,
}),
this.config.i18n[type]
this.config.i18n[type],
);
const value = utils.createElement('span', {
@ -1088,7 +1109,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-home`,
'aria-expanded': false,
},
this.config.i18n[type]
this.config.i18n[type],
);
pane.appendChild(back);
@ -1221,7 +1242,7 @@ const controls = {
this.config.selectors.labels,
' .',
this.config.classNames.hidden,
].join('')
].join(''),
);
Array.from(labels).forEach(label => {

View File

@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.0-beta.17/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.0.0-beta.20/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -259,7 +259,7 @@ const defaults = {
// Ads
'adsloaded',
'adscontentpause',
'adsconentresume',
'adscontentresume',
'adstarted',
'adsmidpoint',
'adscomplete',
@ -373,9 +373,10 @@ const defaults = {
},
// Advertisements plugin
// Tag is not required as publisher is determined by vi.ai using the domain
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
ads: {
enabled: false,
publisherId: '918848828995742',
},
};

View File

@ -10,192 +10,209 @@ import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const listeners = {
// Global listeners
global() {
let last = null;
class Listeners {
constructor(player) {
this.player = player;
this.lastKey = null;
// Get the key code for an event
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
}
// Handle key press
const handleKey = event => {
const code = getKeyCode(event);
const pressed = event.type === 'keydown';
const repeat = pressed && code === last;
// Handle key presses
handleKey(event) {
const code = event.keyCode ? event.keyCode : event.which;
const pressed = event.type === 'keydown';
const repeat = pressed && code === this.lastKey;
// Bail if a modifier key is set
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
// Bail if a modifier key is set
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
if (!utils.is.number(code)) {
return;
}
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
if (!utils.is.number(code)) {
return;
}
// Seek by the number keys
const seekByKey = () => {
// Divide the max duration into 10th's and times by the number value
this.currentTime = this.duration / 10 * (code - 48);
};
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
const preventDefault = [
48,
49,
50,
51,
52,
53,
54,
56,
57,
32,
75,
38,
40,
77,
39,
37,
70,
67,
73,
76,
79,
];
// Check focused element
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
return;
}
// If the code is found prevent default (e.g. prevent scrolling for arrows)
if (preventDefault.includes(code)) {
event.preventDefault();
event.stopPropagation();
}
switch (code) {
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// 0-9
if (!repeat) {
seekByKey();
}
break;
case 32:
case 75:
// Space and K key
if (!repeat) {
this.togglePlay();
}
break;
case 38:
// Arrow up
this.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.decreaseVolume(0.1);
break;
case 77:
// M key
if (!repeat) {
this.muted = !this.muted;
}
break;
case 39:
// Arrow forward
this.forward();
break;
case 37:
// Arrow back
this.rewind();
break;
case 70:
// F key
this.fullscreen.toggle();
break;
case 67:
// C key
if (!repeat) {
this.toggleCaptions();
}
break;
case 76:
// L key
this.loop = !this.loop;
break;
/* case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
default:
break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) {
this.fullscreen.toggle();
}
// Store last code for next cycle
last = code;
} else {
last = null;
}
// Seek by the number keys
const seekByKey = () => {
// Divide the max duration into 10th's and times by the number value
this.player.currentTime = this.player.duration / 10 * (code - 48);
};
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
const preventDefault = [
48,
49,
50,
51,
52,
53,
54,
56,
57,
32,
75,
38,
40,
77,
39,
37,
70,
67,
73,
76,
79,
];
// Check focused element
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
return;
}
// If the code is found prevent default (e.g. prevent scrolling for arrows)
if (preventDefault.includes(code)) {
event.preventDefault();
event.stopPropagation();
}
switch (code) {
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// 0-9
if (!repeat) {
seekByKey();
}
break;
case 32:
case 75:
// Space and K key
if (!repeat) {
this.player.togglePlay();
}
break;
case 38:
// Arrow up
this.player.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.player.decreaseVolume(0.1);
break;
case 77:
// M key
if (!repeat) {
this.player.muted = !this.player.muted;
}
break;
case 39:
// Arrow forward
this.player.forward();
break;
case 37:
// Arrow back
this.player.rewind();
break;
case 70:
// F key
this.player.fullscreen.toggle();
break;
case 67:
// C key
if (!repeat) {
this.player.toggleCaptions();
}
break;
case 76:
// L key
this.player.loop = !this.player.loop;
break;
/* case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
default:
break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!this.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
this.player.fullscreen.toggle();
}
// Store last code for next cycle
this.lastKey = code;
} else {
this.lastKey = null;
}
}
// Toggle menu
toggleMenu(event) {
controls.toggleMenu.call(this.player, event);
}
// Global window & document listeners
global(toggle = true) {
// Keyboard shortcuts
if (this.config.keyboard.global) {
utils.on(window, 'keydown keyup', handleKey, false);
} else if (this.config.keyboard.focused) {
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
if (this.player.config.keyboard.global) {
utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
}
// Click anywhere closes menu
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
}
// Container listeners
container() {
// Keyboard shortcuts
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false);
}
// Detect tab focus
// Remove class on blur/focusout
utils.on(this.elements.container, 'focusout', event => {
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
utils.on(this.player.elements.container, 'focusout', event => {
utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
});
// Add classname to tabbed elements
utils.on(this.elements.container, 'keydown', event => {
utils.on(this.player.elements.container, 'keydown', event => {
if (event.keyCode !== 9) {
return;
}
@ -203,65 +220,65 @@ const listeners = {
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
setTimeout(() => {
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
utils.toggleClass(utils.getFocusElement(), this.player.config.classNames.tabFocus, true);
}, 0);
});
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
if (this.player.config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
utils.on(this.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
this.toggleControls(event);
utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
this.player.toggleControls(event);
});
}
},
}
// Listen for media events
media() {
// Time change on media
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
// Display duration
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
utils.on(this.player.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this.player, event));
// Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
utils.on(this.media, 'loadeddata', () => {
utils.toggleHidden(this.elements.volume, !this.hasAudio);
utils.toggleHidden(this.elements.buttons.mute, !this.hasAudio);
utils.on(this.player.media, 'loadeddata', () => {
utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio);
utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
});
// Handle the media finishing
utils.on(this.media, 'ended', () => {
utils.on(this.player.media, 'ended', () => {
// Show poster on end
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) {
// Restart
this.restart();
this.player.restart();
// Re-load media
this.media.load();
this.player.media.load();
}
});
// Check for buffer progress
utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
// Handle native mute
utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
// Handle native play/pause
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
utils.on(this.player.media, 'playing play pause ended', event => ui.checkPlaying.call(this.player, event));
// Loading
utils.on(this.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
// Check if media failed to load
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
// Click video
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
// Re-fetch the wrapper
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
const wrapper = utils.getElement.call(this.player, `.${this.player.config.classNames.video}`);
// Bail if there's no wrapper (this should never happen)
if (!utils.is.element(wrapper)) {
@ -271,25 +288,25 @@ const listeners = {
// On click play, pause ore restart
utils.on(wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.config.hideControls && support.touch && !this.paused) {
if (this.player.config.hideControls && support.touch && !this.player.paused) {
return;
}
if (this.paused) {
this.play();
} else if (this.ended) {
this.restart();
this.play();
if (this.player.paused) {
this.player.play();
} else if (this.player.ended) {
this.player.restart();
this.player.play();
} else {
this.pause();
this.player.pause();
}
});
}
// Disable right click
if (this.supported.ui && this.config.disableContextMenu) {
if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on(
this.media,
this.player.media,
'contextmenu',
event => {
event.preventDefault();
@ -299,50 +316,50 @@ const listeners = {
}
// Volume change
utils.on(this.media, 'volumechange', () => {
utils.on(this.player.media, 'volumechange', () => {
// Save to storage
this.storage.set({ volume: this.volume, muted: this.muted });
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
});
// Speed change
utils.on(this.media, 'ratechange', () => {
utils.on(this.player.media, 'ratechange', () => {
// Update UI
controls.updateSetting.call(this, 'speed');
controls.updateSetting.call(this.player, 'speed');
// Save to storage
this.storage.set({ speed: this.speed });
this.player.storage.set({ speed: this.player.speed });
});
// Quality change
utils.on(this.media, 'qualitychange', () => {
utils.on(this.player.media, 'qualitychange', () => {
// Update UI
controls.updateSetting.call(this, 'quality');
controls.updateSetting.call(this.player, 'quality');
// Save to storage
this.storage.set({ quality: this.quality });
this.player.storage.set({ quality: this.player.quality });
});
// Caption language change
utils.on(this.media, 'languagechange', () => {
utils.on(this.player.media, 'languagechange', () => {
// Update UI
controls.updateSetting.call(this, 'captions');
controls.updateSetting.call(this.player, 'captions');
// Save to storage
this.storage.set({ language: this.language });
this.player.storage.set({ language: this.player.language });
});
// Captions toggle
utils.on(this.media, 'captionsenabled captionsdisabled', () => {
utils.on(this.player.media, 'captionsenabled captionsdisabled', () => {
// Update UI
controls.updateSetting.call(this, 'captions');
controls.updateSetting.call(this.player, 'captions');
// Save to storage
this.storage.set({ captions: this.captions.active });
this.player.storage.set({ captions: this.player.captions.active });
});
// Proxy events to container
// Bubble up key events for Edge
utils.on(this.media, this.config.events.concat([
utils.on(this.player.media, this.player.config.events.concat([
'keyup',
'keydown',
]).join(' '), event => {
@ -350,12 +367,12 @@ const listeners = {
// Get error details from media
if (event.type === 'error') {
detail = this.media.error;
detail = this.player.media.error;
}
utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail);
utils.dispatchEvent.call(this.player, this.player.elements.container, event.type, true, detail);
});
},
}
// Listen for control events
controls() {
@ -364,176 +381,171 @@ const listeners = {
// Trigger custom and default handlers
const proxy = (event, handlerKey, defaultHandler) => {
const customHandler = this.config.listeners[handlerKey];
const customHandler = this.player.config.listeners[handlerKey];
// Execute custom handler
if (utils.is.function(customHandler)) {
customHandler.call(this, event);
customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
defaultHandler.call(this, event);
defaultHandler.call(this.player, event);
}
};
// Play/pause toggle
utils.on(this.elements.buttons.play, 'click', event =>
utils.on(this.player.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => {
this.togglePlay();
this.player.togglePlay();
}),
);
// Pause
utils.on(this.elements.buttons.restart, 'click', event =>
utils.on(this.player.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => {
this.restart();
this.player.restart();
}),
);
// Rewind
utils.on(this.elements.buttons.rewind, 'click', event =>
utils.on(this.player.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => {
this.rewind();
this.player.rewind();
}),
);
// Rewind
utils.on(this.elements.buttons.forward, 'click', event =>
utils.on(this.player.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => {
this.forward();
this.player.forward();
}),
);
// Mute toggle
utils.on(this.elements.buttons.mute, 'click', event =>
utils.on(this.player.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => {
this.muted = !this.muted;
this.player.muted = !this.player.muted;
}),
);
// Captions toggle
utils.on(this.elements.buttons.captions, 'click', event =>
utils.on(this.player.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => {
this.toggleCaptions();
this.player.toggleCaptions();
}),
);
// Fullscreen toggle
utils.on(this.elements.buttons.fullscreen, 'click', event =>
utils.on(this.player.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => {
this.fullscreen.toggle();
this.player.fullscreen.toggle();
}),
);
// Picture-in-Picture
utils.on(this.elements.buttons.pip, 'click', event =>
utils.on(this.player.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => {
this.pip = 'toggle';
this.player.pip = 'toggle';
}),
);
// Airplay
utils.on(this.elements.buttons.airplay, 'click', event =>
utils.on(this.player.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => {
this.airplay();
this.player.airplay();
}),
);
// Settings menu
utils.on(this.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this, event);
});
// Click anywhere closes menu
utils.on(document.documentElement, 'click', event => {
controls.toggleMenu.call(this, event);
utils.on(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
utils.on(this.elements.settings.form, 'click', event => {
utils.on(this.player.elements.settings.form, 'click', event => {
event.stopPropagation();
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(event, 'language', () => {
this.language = event.target.value;
this.player.language = event.target.value;
});
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(event, 'quality', () => {
this.quality = event.target.value;
this.player.quality = event.target.value;
});
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(event, 'speed', () => {
this.speed = parseFloat(event.target.value);
this.player.speed = parseFloat(event.target.value);
});
} else {
controls.showTab.call(this, event);
controls.showTab.call(this.player, event);
}
});
// Seek
utils.on(this.elements.inputs.seek, inputEvent, event =>
utils.on(this.player.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => {
this.currentTime = event.target.value / event.target.max * this.duration;
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
}),
);
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
utils.on(this.elements.display.currentTime, 'click', () => {
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
utils.on(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.currentTime === 0) {
if (this.player.currentTime === 0) {
return;
}
this.config.invertTime = !this.config.invertTime;
ui.timeUpdate.call(this);
this.player.config.invertTime = !this.player.config.invertTime;
ui.timeUpdate.call(this.player);
});
}
// Volume
utils.on(this.elements.inputs.volume, inputEvent, event =>
utils.on(this.player.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => {
this.volume = event.target.value;
this.player.volume = event.target.value;
}),
);
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this, event.target);
utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this.player, event.target);
});
}
// Seek tooltip
utils.on(this.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this, event));
utils.on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
if (this.player.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
this.elements.controls.hover = event.type === 'mouseenter';
utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = event.type === 'mouseenter';
});
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.elements.controls.pressed = [
utils.on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = [
'mousedown',
'touchstart',
].includes(event.type);
});
// Focus in/out on controls
utils.on(this.elements.controls, 'focusin focusout', event => {
this.toggleControls(event);
utils.on(this.player.elements.controls, 'focusin focusout', event => {
this.player.toggleControls(event);
});
}
// Mouse wheel for volume
utils.on(
this.elements.inputs.volume,
this.player.elements.inputs.volume,
'wheel',
event =>
proxy(event, 'volume', () => {
@ -546,10 +558,10 @@ const listeners = {
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.decreaseVolume(step);
this.player.decreaseVolume(step);
direction = -1;
} else {
this.increaseVolume(step);
this.player.increaseVolume(step);
direction = 1;
}
}
@ -557,22 +569,27 @@ const listeners = {
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.increaseVolume(step);
this.player.increaseVolume(step);
direction = 1;
} else {
this.decreaseVolume(step);
this.player.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if ((direction === 1 && this.media.volume < 1) || (direction === -1 && this.media.volume > 0)) {
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault();
}
}),
false,
);
},
};
}
export default listeners;
// Reset on destroy
clear() {
this.global(false);
}
}
export default Listeners;

View File

@ -8,22 +8,6 @@
import utils from '../utils';
// Build the default tag URL
const getTagUrl = () => {
const params = {
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
AV_CHANNELID: '5a0458dc28a06145e4519d21',
AV_URL: '127.0.0.1:3000',
cb: 1,
AV_WIDTH: 640,
AV_HEIGHT: 480,
};
const base = 'https://go.aniview.com/api/adserver6/vast/';
return `${base}?${utils.buildUrlParams(params)}`;
};
class Ads {
/**
* Ads constructor.
@ -32,39 +16,10 @@ class Ads {
*/
constructor(player) {
this.player = player;
this.enabled = player.config.ads.enabled;
this.publisherId = player.config.ads.publisherId;
this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length;
this.playing = false;
this.initialized = false;
this.blocked = false;
this.enabled = utils.is.url(player.config.ads.tag);
// Check if a tag URL is provided.
if (!this.enabled) {
return;
}
// Check if the Google IMA3 SDK is loaded or load it ourselves
if (!utils.is.object(window.google)) {
utils.loadScript(
player.config.urls.googleIMA.api,
() => {
this.ready();
},
() => {
// Script failed to load or is blocked
this.blocked = true;
this.player.debug.log('Ads error: Google IMA SDK failed to load');
},
);
} else {
this.ready();
}
}
/**
* Get the ads instance ready.
*/
ready() {
this.elements = {
container: null,
displayContainer: null,
@ -76,32 +31,77 @@ class Ads {
this.safetyTimer = null;
this.countdownTimer = null;
// Set listeners on the Plyr instance
this.listeners();
// Setup a promise to resolve when the IMA manager is ready
this.managerPromise = new Promise((resolve, reject) => {
// The ad is loaded and ready
this.on('loaded', resolve);
// Ads failed
this.on('error', reject);
});
this.load();
}
/**
* Load the IMA SDK
*/
load() {
if (this.enabled) {
// Check if the Google IMA3 SDK is loaded or load it ourselves
if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) {
utils
.loadScript(this.player.config.urls.googleIMA.api)
.then(() => {
this.ready();
})
.catch(() => {
// Script failed to load or is blocked
this.trigger('error', new Error('Google IMA SDK failed to load'));
});
} else {
this.ready();
}
}
}
/**
* Get the ads instance ready
*/
ready() {
// 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()');
});
// Set listeners on the Plyr instance
this.listeners();
// Setup the IMA SDK
this.setupIMA();
}
// Build the default tag URL
get tagUrl() {
const params = {
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
AV_CHANNELID: '5a0458dc28a06145e4519d21',
AV_URL: location.hostname,
cb: Date.now(),
AV_WIDTH: 640,
AV_HEIGHT: 480,
AV_CDIM2: this.publisherId,
};
const base = 'https://go.aniview.com/api/adserver6/vast/';
return `${base}?${utils.buildUrlParams(params)}`;
}
/**
* 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.
@ -114,7 +114,6 @@ class Ads {
// 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);
@ -148,7 +147,7 @@ class Ads {
// Request video ads
const request = new google.ima.AdsRequest();
request.adTagUrl = getTagUrl();
request.adTagUrl = this.tagUrl;
// Specify the linear and nonlinear slot sizes. This helps the SDK
// to select the correct creative if multiple are returned
@ -161,8 +160,6 @@ class Ads {
request.forceNonLinearFullSlot = false;
this.loader.requestAds(request);
this.handleEventListeners('ADS_LOADER_LOADED');
} catch (e) {
this.onAdError(e);
}
@ -174,25 +171,25 @@ class Ads {
*/
pollCountdown(start = false) {
if (!start) {
window.clearInterval(this.countdownTimer);
clearInterval(this.countdownTimer);
this.elements.container.removeAttribute('data-badge-text');
return;
}
const update = () => {
const time = utils.formatTime(this.manager.getRemainingTime());
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
const label = `${this.player.config.i18n.advertisement} - ${time}`;
this.elements.container.setAttribute('data-badge-text', label);
};
this.countdownTimer = window.setInterval(update, 100);
this.countdownTimer = setInterval(update, 100);
}
/**
* This method is called whenever the ads are ready inside the AdDisplayContainer
* @param {Event} adsManagerLoadedEvent
*/
onAdsManagerLoaded(adsManagerLoadedEvent) {
onAdsManagerLoaded(event) {
// Get the ads manager
const settings = new google.ima.AdsRenderingSettings();
@ -202,14 +199,14 @@ 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.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
this.manager = event.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) {
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
const seekElement = this.player.elements.progress;
if (seekElement) {
@ -241,7 +238,7 @@ class Ads {
});
// Resolve our adsManager
this.handleEventListeners('ADS_MANAGER_LOADED');
this.trigger('loaded');
}
/**
@ -259,17 +256,18 @@ class Ads {
// Proxy event
const dispatchEvent = type => {
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
utils.dispatchEvent.call(this.player, this.player.media, event);
};
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');
this.trigger('loaded');
// Bubble event
dispatchEvent('loaded');
dispatchEvent(event.type);
// Start countdown
this.pollCountdown(true);
@ -287,10 +285,9 @@ class Ads {
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');
dispatchEvent(event.type);
// 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.
@ -322,9 +319,8 @@ class Ads {
// 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');
dispatchEvent(event.type);
this.pauseContent();
@ -335,9 +331,8 @@ class Ads {
// 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');
dispatchEvent(event.type);
this.pollCountdown();
@ -346,23 +341,11 @@ class Ads {
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');
break;
case google.ima.AdEvent.Type.IMPRESSION:
dispatchEvent('impression');
break;
case google.ima.AdEvent.Type.CLICK:
dispatchEvent('click');
dispatchEvent(event.type);
break;
default:
@ -376,7 +359,7 @@ class Ads {
*/
onAdError(event) {
this.cancel();
this.player.debug.log('Ads error', event);
this.player.debug.warn('Ads error', event);
}
/**
@ -423,39 +406,41 @@ class Ads {
const { container } = this.player.elements;
if (!this.managerPromise) {
return;
this.resumeContent();
}
// 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();
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);
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();
// 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);
}
this.initialized = true;
} catch (adError) {
// An error may be thrown if there was a problem with the
// VAST response
this.onAdError(adError);
}
});
})
.catch(() => {});
}
/**
* Resume our video.
* Resume our video
*/
resumeContent() {
// Hide our ad container
utils.toggleHidden(this.elements.container, true);
// Hide the advertisement container
this.elements.container.style.zIndex = '';
// Ad is stopped
this.playing = false;
@ -470,8 +455,8 @@ class Ads {
* Pause our video
*/
pauseContent() {
// Show our ad container.
utils.toggleHidden(this.elements.container, false);
// Show the advertisement container
this.elements.container.style.zIndex = 3;
// Ad is playing.
this.playing = true;
@ -493,7 +478,7 @@ class Ads {
}
// Tell our instance that we're done for now
this.handleEventListeners('ERROR');
this.trigger('error');
// Re-create our adsManager
this.loadAds();
@ -504,30 +489,38 @@ class Ads {
*/
loadAds() {
// Tell our adsManager to go bye bye
this.managerPromise.then(() => {
// Destroy our adsManager
if (this.manager) {
this.manager.destroy();
}
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);
});
// Re-set our adsManager promises
this.managerPromise = new Promise(resolve => {
this.on('loaded', resolve);
this.player.debug.log(this.manager);
});
// Now request some new advertisements
this.requestAds();
});
// Now request some new advertisements
this.requestAds();
})
.catch(() => {});
}
/**
* 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);
trigger(event, ...args) {
const handlers = this.events[event];
if (utils.is.array(handlers)) {
handlers.forEach(handler => {
if (utils.is.function(handler)) {
handler.apply(this, args);
}
});
}
}
@ -538,7 +531,12 @@ class Ads {
* @return {Ads}
*/
on(event, callback) {
this.events[event] = callback;
if (!utils.is.array(this.events[event])) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
}

View File

@ -16,9 +16,14 @@ const vimeo = {
// Load the API if not already
if (!utils.is.object(window.Vimeo)) {
utils.loadScript(this.config.urls.vimeo.api, () => {
vimeo.ready.call(this);
});
utils
.loadScript(this.config.urls.vimeo.api)
.then(() => {
vimeo.ready.call(this);
})
.catch(error => {
this.debug.warn('Vimeo API failed to load', error);
});
} else {
vimeo.ready.call(this);
}

View File

@ -19,7 +19,9 @@ const youtube = {
youtube.ready.call(this);
} else {
// Load the API
utils.loadScript(this.config.urls.youtube.api);
utils.loadScript(this.config.urls.youtube.api).catch(error => {
this.debug.warn('YouTube API failed to load', error);
});
// Setup callback for the API
// YouTube has it's own system of course...
@ -303,10 +305,10 @@ const youtube = {
utils.dispatchEvent.call(player, player.media, 'durationchange');
// Reset timer
window.clearInterval(player.timers.buffering);
clearInterval(player.timers.buffering);
// Setup buffering
player.timers.buffering = window.setInterval(() => {
player.timers.buffering = setInterval(() => {
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
@ -320,7 +322,7 @@ const youtube = {
// Bail if we're at 100%
if (player.media.buffered === 1) {
window.clearInterval(player.timers.buffering);
clearInterval(player.timers.buffering);
// Trigger event
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
@ -335,7 +337,7 @@ const youtube = {
const instance = event.target;
// Reset timer
window.clearInterval(player.timers.playing);
clearInterval(player.timers.playing);
// Handle events
// -1 Unstarted
@ -375,7 +377,7 @@ const youtube = {
utils.dispatchEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = window.setInterval(() => {
player.timers.playing = setInterval(() => {
utils.dispatchEvent.call(player, player.media, 'timeupdate');
}, 50);

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v3.0.0-beta.17
// plyr.js v3.0.0-beta.20
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@ -12,12 +12,12 @@ import utils from './utils';
import Console from './console';
import Fullscreen from './fullscreen';
import Listeners from './listeners';
import Storage from './storage';
import Ads from './plugins/ads';
import captions from './captions';
import controls from './controls';
import listeners from './listeners';
import media from './media';
import source from './source';
import ui from './ui';
@ -235,6 +235,9 @@ class Plyr {
return;
}
// Create listeners
this.listeners = new Listeners(this);
// Setup local storage for user settings
this.storage = new Storage(this);
@ -250,9 +253,6 @@ class Plyr {
// Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0);
// Global listeners
listeners.global.call(this);
// Add style hook
ui.addStyleHook.call(this);
@ -272,6 +272,12 @@ class Plyr {
ui.build.call(this);
}
// Container listeners
this.listeners.container();
// Global listeners
this.listeners.global();
// Setup fullscreen
this.fullscreen = new Fullscreen(this);
@ -309,10 +315,9 @@ class Plyr {
* Play the media, or play the advertisement (if they are not blocked)
*/
play() {
// TODO: Always return a promise?
if (this.ads.enabled && !this.ads.initialized && !this.ads.blocked) {
this.ads.play();
return null;
// If ads are enabled, wait for them first
if (this.ads.enabled && !this.ads.initialized) {
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
}
// Return the promise (for HTML5)
@ -946,7 +951,7 @@ class Plyr {
}
// Clear timer on every call
window.clearTimeout(this.timers.controls);
clearTimeout(this.timers.controls);
// If the mouse is not over the controls, set a timeout to hide them
if (show || this.paused || this.loading) {
@ -1052,6 +1057,9 @@ class Plyr {
callback();
}
} else {
// Unbind listeners
this.listeners.clear();
// Replace the container with the original element provided
utils.replaceElement(this.elements.original, this.elements.container);
@ -1063,15 +1071,24 @@ class Plyr {
callback.call(this.elements.original);
}
// Clear for GC
this.elements = null;
// Clear for garbage collection
setTimeout(() => {
this.elements = null;
this.media = null;
}, 200);
}
};
// Stop playback
this.stop();
// Type specific stuff
switch (`${this.provider}:${this.type}`) {
case 'html5:video':
case 'html5:audio':
// Clear timeout
clearTimeout(this.timers.loading);
// Restore native video controls
ui.toggleNativeControls.call(this, true);
@ -1082,8 +1099,8 @@ class Plyr {
case 'youtube:video':
// Clear timers
window.clearInterval(this.timers.buffering);
window.clearInterval(this.timers.playing);
clearInterval(this.timers.buffering);
clearInterval(this.timers.playing);
// Destroy YouTube API
if (this.embed !== null) {

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.0.0-beta.17
// plyr.js v3.0.0-beta.20
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================

View File

@ -26,7 +26,7 @@ const ui = {
build() {
// Re-attach media element listeners
// TODO: Use event bubbling
listeners.media.call(this);
this.listeners.media();
// Don't setup interface if no support
if (!this.supported.ui) {
@ -45,7 +45,7 @@ const ui = {
controls.inject.call(this);
// Re-attach control listeners
listeners.controls.call(this);
this.listeners.controls();
}
// If there's no controls, bail

View File

@ -123,29 +123,29 @@ const utils = {
},
// Load an external script
loadScript(url, callback, error) {
const current = document.querySelector(`script[src="${url}"]`);
loadScript(url) {
return new Promise((resolve, reject) => {
const current = document.querySelector(`script[src="${url}"]`);
// Check script is not already referenced, if so wait for load
if (current !== null) {
current.callbacks = current.callbacks || [];
current.callbacks.push(callback);
return;
}
// Check script is not already referenced, if so wait for load
if (current !== null) {
current.callbacks = current.callbacks || [];
current.callbacks.push(resolve);
return;
}
// Build the element
const element = document.createElement('script');
// Build the element
const element = document.createElement('script');
// Callback queue
element.callbacks = element.callbacks || [];
element.callbacks.push(callback);
// Callback queue
element.callbacks = element.callbacks || [];
element.callbacks.push(resolve);
// Error queue
element.errors = element.errors || [];
element.errors.push(error);
// Error queue
element.errors = element.errors || [];
element.errors.push(reject);
// Bind callback
if (utils.is.function(callback)) {
// Bind callback
element.addEventListener(
'load',
event => {
@ -154,24 +154,24 @@ const utils = {
},
false,
);
}
// Bind error handling
element.addEventListener(
'error',
event => {
element.errors.forEach(err => err.call(null, event));
element.errors = null;
},
false,
);
// Bind error handling
element.addEventListener(
'error',
event => {
element.errors.forEach(err => err.call(null, event));
element.errors = null;
},
false,
);
// Set the URL after binding callback
element.src = url;
// Set the URL after binding callback
element.src = url;
// Inject
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first);
// Inject
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first);
});
},
// Load an external SVG sprite

View File

@ -8,6 +8,7 @@
direction: ltr;
font-family: $plyr-font-family;
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
font-weight: $plyr-font-weight-regular;
line-height: $plyr-line-height;
max-width: 100%;

View File

@ -9,8 +9,17 @@
position: absolute;
right: 0;
top: 0;
z-index: 3; // Above the controls
z-index: -1; // Hide it by default
// Make sure the inner container is big enough for the ad creative.
> div,
> div iframe {
height: 100%;
position: absolute;
width: 100%;
}
// The countdown label
&::after {
background: rgba($plyr-color-gunmetal, 0.8);
border-radius: 2px;

View File

@ -953,10 +953,14 @@ browserslist@^3.1.1:
caniuse-lite "^1.0.30000809"
electron-to-chromium "^1.3.33"
builtin-modules@^1.0.0, builtin-modules@^1.1.0, builtin-modules@^1.1.1:
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
builtin-modules@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@ -1125,9 +1129,9 @@ class-utils@^0.3.5:
lazy-cache "^2.0.2"
static-extend "^0.1.1"
clean-css@4.1.9:
version "4.1.9"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301"
clean-css@4.1.11:
version "4.1.11"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a"
dependencies:
source-map "0.5.x"
@ -2144,9 +2148,11 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
git-branch@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/git-branch/-/git-branch-1.0.0.tgz#64cc7dd75da2d81a9d4679087c1f8b56e6bd2d3d"
git-branch@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/git-branch/-/git-branch-2.0.1.tgz#442724a97574c66ede4290ded75b7430785da08f"
dependencies:
findup-sync "^2.0.0"
glob-base@^0.3.0:
version "0.3.0"
@ -2347,12 +2353,12 @@ gulp-better-rollup@^3.0.0:
vinyl "^2.1.0"
vinyl-sourcemaps-apply "^0.2.1"
gulp-clean-css@^3.9.2:
version "3.9.2"
resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-3.9.2.tgz#b280e6f56bf8bee39e697e77aaa72d3e7e7d3bd5"
gulp-clean-css@^3.9.3:
version "3.9.3"
resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-3.9.3.tgz#47bf7ad62f44970f86e4ac4bdeed68ad904e65c5"
dependencies:
clean-css "4.1.9"
plugin-error "0.1.2"
clean-css "4.1.11"
plugin-error "1.0.1"
through2 "2.0.3"
vinyl-sourcemaps-apply "0.2.1"
@ -4178,7 +4184,16 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
plugin-error@0.1.2, plugin-error@^0.1.2:
plugin-error@1.0.1, plugin-error@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
dependencies:
ansi-colors "^1.0.1"
arr-diff "^4.0.0"
arr-union "^3.1.0"
extend-shallow "^3.0.2"
plugin-error@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
dependencies:
@ -4188,15 +4203,6 @@ plugin-error@0.1.2, plugin-error@^0.1.2:
arr-union "^2.0.1"
extend-shallow "^1.1.2"
plugin-error@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
dependencies:
ansi-colors "^1.0.1"
arr-diff "^4.0.0"
arr-union "^3.1.0"
extend-shallow "^3.0.2"
plugin-log@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/plugin-log/-/plugin-log-0.1.0.tgz#86049cf6ab10833398a931f3689cbaee7b5e1333"
@ -4721,9 +4727,9 @@ rollup-plugin-babel@^3.0.3:
dependencies:
rollup-pluginutils "^1.5.0"
rollup-plugin-commonjs@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.3.0.tgz#91b4ba18f340951e39ed7b1901f377a80ab3f9c3"
rollup-plugin-commonjs@^8.4.1:
version "8.4.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz#5c9cea2b2c3de322f5fbccd147e07ed5e502d7a0"
dependencies:
acorn "^5.2.1"
estree-walker "^0.5.0"
@ -4731,11 +4737,11 @@ rollup-plugin-commonjs@^8.3.0:
resolve "^1.4.0"
rollup-pluginutils "^2.0.1"
rollup-plugin-node-resolve@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.0.3.tgz#8f57b253edd00e5b0ad0aed7b7e9cf5982e98fa4"
rollup-plugin-node-resolve@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.2.0.tgz#31534952f3ab21f9473c1d092be7ed43937ea4d4"
dependencies:
builtin-modules "^1.1.0"
builtin-modules "^2.0.0"
is-module "^1.0.0"
resolve "^1.1.6"
@ -5153,9 +5159,11 @@ style-search@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
stylelint-config-prettier@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-2.1.0.tgz#395874225ceef02ea8e31c2f4073098f4505b054"
stylelint-config-prettier@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-3.0.4.tgz#1259093c1db2d5e8dfff7d9672f6f41db2101845"
dependencies:
stylelint "^9.1.1"
stylelint-config-recommended@^2.1.0:
version "2.1.0"
@ -5184,7 +5192,7 @@ stylelint-order@^0.8.1:
postcss "^6.0.14"
postcss-sorting "^3.1.0"
stylelint-scss@^2.0.0, stylelint-scss@^2.4.0:
stylelint-scss@^2.0.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-2.4.0.tgz#2d417688556d64536f435e7a6f77a40dbb89e505"
dependencies:
@ -5194,6 +5202,16 @@ stylelint-scss@^2.0.0, stylelint-scss@^2.4.0:
postcss-selector-parser "^3.1.1"
postcss-value-parser "^3.3.0"
stylelint-scss@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-2.5.0.tgz#ac4c83474c53b19cc1f9e93d332786cf89c8d217"
dependencies:
lodash "^4.17.4"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
postcss-selector-parser "^3.1.1"
postcss-value-parser "^3.3.0"
stylelint-selector-bem-pattern@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/stylelint-selector-bem-pattern/-/stylelint-selector-bem-pattern-2.0.0.tgz#9a6130c9c90963b30e925c917079d6c8fed73f45"