Compare commits
29 Commits
v3.0.0-bet
...
v3.0.0
Author | SHA1 | Date | |
---|---|---|---|
3bccc0da01 | |||
a0173d991e | |||
600f0eb8a3 | |||
5db73b1327 | |||
5cb1628cd8 | |||
c74b75e8e1 | |||
0538476d6f | |||
5ebe18d081 | |||
207adde36d | |||
1b13ddaa54 | |||
9981c349be | |||
b3365a7373 | |||
9a0c1c830d | |||
ef27ba16f4 | |||
e206edc1f6 | |||
c734bc4957 | |||
572b8a7aca | |||
eebae4a227 | |||
e0562752ea | |||
6a2ca534d2 | |||
7adc2bc6c8 | |||
ba8d7831a7 | |||
69ffcbad27 | |||
819f7d1080 | |||
409b588458 | |||
e90a603d57 | |||
e6db374a72 | |||
ab7f277a1b | |||
d5a1a7ca1c |
@ -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
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
7
demo/dist/demo.js
vendored
7
demo/dist/demo.js
vendored
@ -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
|
||||
},
|
||||
@ -55,7 +55,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
||||
},
|
||||
ads: {
|
||||
enabled: true
|
||||
enabled: true,
|
||||
publisherId: '918848828995742'
|
||||
}
|
||||
});
|
||||
|
||||
@ -231,7 +232,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
demo/dist/demo.js.map
vendored
2
demo/dist/demo.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.min.js
vendored
2
demo/dist/demo.min.js
vendored
@ -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,publisherId:"918848828995742"}});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
|
||||
|
2
demo/dist/demo.min.js.map
vendored
2
demo/dist/demo.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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>
|
||||
|
@ -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,
|
||||
},
|
||||
@ -53,6 +53,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
},
|
||||
ads: {
|
||||
enabled: true,
|
||||
publisherId: '918848828995742',
|
||||
},
|
||||
});
|
||||
|
||||
@ -238,7 +239,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
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
3168
dist/plyr.js
vendored
3168
dist/plyr.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js
vendored
2
dist/plyr.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js.map
vendored
2
dist/plyr.min.js.map
vendored
File diff suppressed because one or more lines are too long
3170
dist/plyr.polyfilled.js
vendored
3170
dist/plyr.polyfilled.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/plyr.polyfilled.js.map
vendored
2
dist/plyr.polyfilled.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
2
dist/plyr.polyfilled.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js.map
vendored
2
dist/plyr.polyfilled.min.js.map
vendored
File diff suppressed because one or more lines are too long
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "3.0.0-beta.17",
|
||||
"version": "3.0.0",
|
||||
"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": "^9.1.3",
|
||||
"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"],
|
||||
|
26
readme.md
26
readme.md
@ -1,7 +1,3 @@
|
||||
---
|
||||
Beware: This version is currently in beta and not production-ready
|
||||
---
|
||||
|
||||
# Plyr
|
||||
|
||||
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
||||
@ -13,13 +9,13 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
|
||||
## Features
|
||||
|
||||
* **Accessible** - full support for VTT captions and screen readers
|
||||
* **Lightweight** - just 18KB minified and gzipped
|
||||
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||
`<span>` or `<a href="#">` button hacks
|
||||
* **Responsive** - works with any screen size
|
||||
* **HTML Video & Audio** - support for both formats
|
||||
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||
* **[Monetization](#ads)** - make money from your videos
|
||||
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
||||
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||
@ -128,7 +124,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/plyr.js"></script>
|
||||
```
|
||||
|
||||
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
||||
@ -144,13 +140,23 @@ 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/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/plyr.svg`.
|
||||
|
||||
## Ads
|
||||
|
||||
Plyr has partnered up with [ai.vi](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||
|
||||
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||
* Grab your publisher ID from the code snippet
|
||||
* Enable ads in the [config options](#options) and enter your publisher ID
|
||||
|
||||
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||
|
||||
## Advanced
|
||||
|
||||
@ -278,12 +284,13 @@ 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. |
|
||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||
|
||||
1. Vimeo only
|
||||
|
||||
@ -366,6 +373,7 @@ player.fullscreen.active; // false;
|
||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||
|
59
src/js/controls.js
vendored
59
src/js/controls.js
vendored
@ -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 => {
|
||||
|
@ -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/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: '',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
111
src/js/plyr.js
111
src/js/plyr.js
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.0.0-beta.17
|
||||
// plyr.js v3.0.0
|
||||
// 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);
|
||||
|
||||
@ -287,32 +293,31 @@ class Plyr {
|
||||
* Types and provider helpers
|
||||
*/
|
||||
get isHTML5() {
|
||||
return this.provider === providers.html5;
|
||||
return Boolean(this.provider === providers.html5);
|
||||
}
|
||||
get isEmbed() {
|
||||
return this.isYouTube || this.isVimeo;
|
||||
return Boolean(this.isYouTube || this.isVimeo);
|
||||
}
|
||||
get isYouTube() {
|
||||
return this.provider === providers.youtube;
|
||||
return Boolean(this.provider === providers.youtube);
|
||||
}
|
||||
get isVimeo() {
|
||||
return this.provider === providers.vimeo;
|
||||
return Boolean(this.provider === providers.vimeo);
|
||||
}
|
||||
get isVideo() {
|
||||
return this.type === types.video;
|
||||
return Boolean(this.type === types.video);
|
||||
}
|
||||
get isAudio() {
|
||||
return this.type === types.audio;
|
||||
return Boolean(this.type === types.audio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@ -334,21 +339,21 @@ class Plyr {
|
||||
* Get paused state
|
||||
*/
|
||||
get paused() {
|
||||
return this.media.paused;
|
||||
return Boolean(this.media.paused);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get playing state
|
||||
*/
|
||||
get playing() {
|
||||
return !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true);
|
||||
return Boolean(!this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ended state
|
||||
*/
|
||||
get ended() {
|
||||
return this.media.ended;
|
||||
return Boolean(this.media.ended);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,11 +434,32 @@ class Plyr {
|
||||
return Number(this.media.currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get buffered
|
||||
*/
|
||||
get buffered() {
|
||||
const { buffered } = this.media;
|
||||
|
||||
// YouTube / Vimeo return a float between 0-1
|
||||
if (utils.is.number(buffered)) {
|
||||
return buffered;
|
||||
}
|
||||
|
||||
// HTML5
|
||||
// TODO: Handle buffered chunks of the media
|
||||
// (i.e. seek to another section buffers only that section)
|
||||
if (buffered && buffered.length && this.duration > 0) {
|
||||
return buffered.end(0) / this.duration;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seeking status
|
||||
*/
|
||||
get seeking() {
|
||||
return this.media.seeking;
|
||||
return Boolean(this.media.seeking);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -498,7 +524,7 @@ class Plyr {
|
||||
* Get the current player volume
|
||||
*/
|
||||
get volume() {
|
||||
return this.media.volume;
|
||||
return Number(this.media.volume);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -547,7 +573,7 @@ class Plyr {
|
||||
* Get current muted state
|
||||
*/
|
||||
get muted() {
|
||||
return this.media.muted;
|
||||
return Boolean(this.media.muted);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -564,12 +590,16 @@ class Plyr {
|
||||
}
|
||||
|
||||
// Get audio tracks
|
||||
return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
|
||||
return (
|
||||
Boolean(this.media.mozHasAudio) ||
|
||||
Boolean(this.media.webkitAudioDecodedByteCount) ||
|
||||
Boolean(this.media.audioTracks && this.media.audioTracks.length)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set playback speed
|
||||
* @param {decimal} speed - the speed of playback (0.5-2.0)
|
||||
* @param {number} speed - the speed of playback (0.5-2.0)
|
||||
*/
|
||||
set speed(input) {
|
||||
let speed = null;
|
||||
@ -610,7 +640,7 @@ class Plyr {
|
||||
* Get current playback speed
|
||||
*/
|
||||
get speed() {
|
||||
return this.media.playbackRate;
|
||||
return Number(this.media.playbackRate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -710,7 +740,7 @@ class Plyr {
|
||||
* Get current loop state
|
||||
*/
|
||||
get loop() {
|
||||
return this.media.loop;
|
||||
return Boolean(this.media.loop);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -767,7 +797,7 @@ class Plyr {
|
||||
* Get the current autoplay state
|
||||
*/
|
||||
get autoplay() {
|
||||
return this.config.autoplay;
|
||||
return Boolean(this.config.autoplay);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -946,7 +976,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) {
|
||||
@ -1024,6 +1054,10 @@ class Plyr {
|
||||
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
||||
*/
|
||||
destroy(callback, soft = false) {
|
||||
if (!this.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
const done = () => {
|
||||
// Reset overflow (incase destroyed while in fullscreen)
|
||||
document.body.style.overflow = '';
|
||||
@ -1052,6 +1086,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 +1100,27 @@ class Plyr {
|
||||
callback.call(this.elements.original);
|
||||
}
|
||||
|
||||
// Clear for GC
|
||||
this.elements = null;
|
||||
// Reset state
|
||||
this.ready = false;
|
||||
|
||||
// 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 +1131,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) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.0.0-beta.17
|
||||
// plyr.js v3.0.0
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
@ -30,13 +30,9 @@ const support = {
|
||||
break;
|
||||
|
||||
case 'youtube:video':
|
||||
api = true;
|
||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
break;
|
||||
|
||||
case 'vimeo:video':
|
||||
api = true;
|
||||
ui = support.rangeInput && !browser.isIPhone;
|
||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
23
src/js/ui.js
23
src/js/ui.js
@ -5,7 +5,6 @@
|
||||
import utils from './utils';
|
||||
import captions from './captions';
|
||||
import controls from './controls';
|
||||
import listeners from './listeners';
|
||||
|
||||
const ui = {
|
||||
addStyleHook() {
|
||||
@ -25,8 +24,8 @@ const ui = {
|
||||
// Setup the UI
|
||||
build() {
|
||||
// Re-attach media element listeners
|
||||
// TODO: Use event bubbling
|
||||
listeners.media.call(this);
|
||||
// TODO: Use event bubbling?
|
||||
this.listeners.media();
|
||||
|
||||
// Don't setup interface if no support
|
||||
if (!this.supported.ui) {
|
||||
@ -45,7 +44,7 @@ const ui = {
|
||||
controls.inject.call(this);
|
||||
|
||||
// Re-attach control listeners
|
||||
listeners.controls.call(this);
|
||||
this.listeners.controls();
|
||||
}
|
||||
|
||||
// If there's no controls, bail
|
||||
@ -256,21 +255,7 @@ const ui = {
|
||||
// Check buffer status
|
||||
case 'playing':
|
||||
case 'progress':
|
||||
value = (() => {
|
||||
const { buffered } = this.media;
|
||||
|
||||
if (buffered && buffered.length) {
|
||||
// HTML5
|
||||
return utils.getPercentage(buffered.end(0), this.duration);
|
||||
} else if (utils.is.number(buffered)) {
|
||||
// YouTube returns between 0 and 1
|
||||
return buffered * 100;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})();
|
||||
|
||||
ui.setProgress.call(this, this.elements.display.buffer, value);
|
||||
ui.setProgress.call(this, this.elements.display.buffer, this.buffered * 100);
|
||||
|
||||
break;
|
||||
|
||||
|
@ -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
|
||||
@ -671,6 +671,7 @@ const utils = {
|
||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (current / max * 100).toFixed(2);
|
||||
},
|
||||
|
||||
|
@ -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%;
|
||||
|
@ -3,14 +3,25 @@
|
||||
// ==========================================================================
|
||||
|
||||
.plyr__ads {
|
||||
border-radius: inherit;
|
||||
bottom: 0;
|
||||
cursor: pointer;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
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;
|
||||
|
Reference in New Issue
Block a user