commit
9d51291125
30
.eslintrc
Normal file
30
.eslintrc
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": ["airbnb-base", "prettier"],
|
||||||
|
"plugins": ["simple-import-sort", "import"],
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"Plyr": false,
|
||||||
|
"jQuery": false
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"import/no-cycle": "warn",
|
||||||
|
"padding-line-between-statements": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"blankLine": "never",
|
||||||
|
"prev": ["singleline-const", "singleline-let", "singleline-var"],
|
||||||
|
"next": ["singleline-const", "singleline-let", "singleline-var"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort-imports": "off",
|
||||||
|
"import/order": "off",
|
||||||
|
"simple-import-sort/sort": "error"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"extends": ["airbnb-base", "prettier"],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"Plyr": false,
|
|
||||||
"jQuery": false
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"import/no-cycle": 1,
|
|
||||||
"no-const-assign": 1,
|
|
||||||
"no-shadow": 0,
|
|
||||||
"no-this-before-super": 1,
|
|
||||||
"no-undef": 1,
|
|
||||||
"no-unreachable": 1,
|
|
||||||
"no-unused-vars": 1,
|
|
||||||
"constructor-super": 1,
|
|
||||||
"valid-typeof": 1,
|
|
||||||
"indent": [2, 4, { "SwitchCase": 1 }],
|
|
||||||
"quotes": [2, "single", "avoid-escape"],
|
|
||||||
"semi": [2, "always"],
|
|
||||||
"eqeqeq": [2, "always"],
|
|
||||||
"one-var": [2, "never"],
|
|
||||||
"comma-dangle": [2, "always-multiline"],
|
|
||||||
"spaced-comment": [2, "always"],
|
|
||||||
"no-restricted-globals": 2,
|
|
||||||
"no-param-reassign": [2, { "props": false }]
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module"
|
|
||||||
}
|
|
||||||
}
|
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: sampotts
|
||||||
|
patreon: plyr
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,9 +3,7 @@ node_modules
|
|||||||
credentials.json
|
credentials.json
|
||||||
*.mp4
|
*.mp4
|
||||||
!dist/blank.mp4
|
!dist/blank.mp4
|
||||||
index-*.html
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
package-lock.json
|
|
||||||
*.webm
|
*.webm
|
||||||
.idea/
|
.idea/
|
||||||
|
@ -2,8 +2,9 @@ demo
|
|||||||
.github
|
.github
|
||||||
.vscode
|
.vscode
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
build.json
|
||||||
credentials.json
|
credentials.json
|
||||||
bundles.json
|
deploy.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
*.mp4
|
*.mp4
|
||||||
|
@ -10,13 +10,15 @@
|
|||||||
"src": "./src/js/plyr.polyfilled.js",
|
"src": "./src/js/plyr.polyfilled.js",
|
||||||
"dist": "./dist/",
|
"dist": "./dist/",
|
||||||
"formats": ["es", "umd"],
|
"formats": ["es", "umd"],
|
||||||
"namespace": "Plyr"
|
"namespace": "Plyr",
|
||||||
|
"polyfill": true
|
||||||
},
|
},
|
||||||
"demo.js": {
|
"demo.js": {
|
||||||
"src": "./demo/src/js/demo.js",
|
"src": "./demo/src/js/demo.js",
|
||||||
"dist": "./demo/dist/",
|
"dist": "./demo/dist/",
|
||||||
"formats": ["iife"],
|
"formats": ["iife"],
|
||||||
"namespace": "Demo"
|
"namespace": "Demo",
|
||||||
|
"polyfill": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"css": {
|
"css": {
|
||||||
|
40
changelog.md
40
changelog.md
@ -1,3 +1,43 @@
|
|||||||
|
## v3.5.6
|
||||||
|
|
||||||
|
- Another Edge fix (thanks Nick Hawk via Slack)
|
||||||
|
|
||||||
|
## v3.5.5
|
||||||
|
|
||||||
|
- YouTube fix for when there are other embeds on the page (thanks @aFarkas)
|
||||||
|
- Separated demo dependencies into their own package.json
|
||||||
|
- Fix for Edge controls flexbox issue when resizing the player (thanks Nick Hawk via Slack)
|
||||||
|
- More aspect ratio fixes
|
||||||
|
|
||||||
|
## v3.5.4
|
||||||
|
|
||||||
|
- Added: Set download URL via new setter
|
||||||
|
- Improvement: The order of the `controls` option now effects the order in the DOM - i.e. you can re-order the controls - Note: this may break any custom CSS you have setup. Please see the changes in the PR to the default SASS
|
||||||
|
- Fixed issue with empty controls and preview thumbs
|
||||||
|
- Fixed issue with setGutter call (from Sentry)
|
||||||
|
- Fixed issue with initial selected speed not working
|
||||||
|
- Added notes on `autoplay` config option and browser compatibility
|
||||||
|
- Fixed issue with ads volume not matching current content volume
|
||||||
|
- Fixed race condition where ads were loading during source change
|
||||||
|
- Improvement: Automatic aspect ratio for YouTube is now supported, meaning all aspect ratios are set based on media content - Note: we're now using a different API to get YouTube video metadata so you may need to adjust any CSPs you have setup
|
||||||
|
- Fix for menu in the Shadow DOM (thanks @emielbeinema)
|
||||||
|
|
||||||
|
## v3.5.3
|
||||||
|
|
||||||
|
- Improved the usage of the `ratio` config option; it now works as expected and for all video types. The default has not changed, it is to dynamically, where possible (except YouTube where 16:9 is used) determine the ratio from the media source so this is not a breaking change.
|
||||||
|
- Added new `ratio` getter and setter
|
||||||
|
- Fix: Properly clear all timeouts on destroy
|
||||||
|
- Fix: Allow absolute paths in preview thumbnails
|
||||||
|
- Improvement: Allow optional hours and ms in VTT parser in preview thumbnails
|
||||||
|
|
||||||
|
## v3.5.2
|
||||||
|
|
||||||
|
- Fixed issue where the preview thumbnail was present while scrubbing
|
||||||
|
|
||||||
|
## v3.5.1
|
||||||
|
|
||||||
|
- Fixed build issues with babel and browserslist
|
||||||
|
|
||||||
## v3.5.0
|
## v3.5.0
|
||||||
|
|
||||||
- Preview seek/scrubbing thumbnails (thanks @jamesoflol)
|
- Preview seek/scrubbing thumbnails (thanks @jamesoflol)
|
||||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
22426
demo/dist/demo.js
vendored
22426
demo/dist/demo.js
vendored
File diff suppressed because it is too large
Load Diff
2
demo/dist/demo.min.js
vendored
2
demo/dist/demo.min.js
vendored
File diff suppressed because one or more lines are too long
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
2
demo/dist/error.css
vendored
2
demo/dist/error.css
vendored
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@
|
|||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
||||||
|
|
||||||
<!-- Opengraph -->
|
<!-- Open Graph -->
|
||||||
<meta
|
<meta
|
||||||
property="og:title"
|
property="og:title"
|
||||||
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
||||||
@ -50,12 +50,23 @@
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Google Analytics-->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'UA-132699580-1');
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<header>
|
<header>
|
||||||
<h1>Plyr</h1>
|
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
||||||
<p>
|
<p>
|
||||||
A simple, accessible and customisable media player for
|
A simple, accessible and customisable media player for
|
||||||
<button type="button" class="faux-link" data-source="video">
|
<button type="button" class="faux-link" data-source="video">
|
||||||
@ -98,7 +109,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Premium video monitization from
|
Premium video monetization from
|
||||||
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" 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" />
|
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
||||||
<span class="sr-only">ai.vi</span>
|
<span class="sr-only">ai.vi</span>
|
||||||
@ -106,8 +117,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="call-to-action">
|
<div class="call-to-action">
|
||||||
<span class="button--with-count">
|
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
||||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr-button">
|
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>GitHub</title>
|
<title>GitHub</title>
|
||||||
<path
|
<path
|
||||||
@ -121,7 +131,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Download on GitHub
|
Download on GitHub
|
||||||
</a>
|
</a>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -256,23 +265,13 @@
|
|||||||
<a
|
<a
|
||||||
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="js-shr-button"
|
class="js-shr"
|
||||||
>tweet it</a
|
>tweet it</a
|
||||||
>
|
>
|
||||||
👍
|
👍
|
||||||
</p>
|
</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Polyfills -->
|
|
||||||
<script
|
|
||||||
src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values,URL,Math.trunc"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
|
|
||||||
<!-- Sharing libary (https://shr.one) -->
|
|
||||||
<script src="https://cdn.shr.one/2.0.0-beta.2/shr.js" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<!-- Docs script -->
|
|
||||||
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
14
demo/package.json
Normal file
14
demo/package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "plyr-demo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Demo for Plyr",
|
||||||
|
"homepage": "https://plyr.io",
|
||||||
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.1.4",
|
||||||
|
"custom-event-polyfill": "^1.0.7",
|
||||||
|
"raven-js": "^3.27.2",
|
||||||
|
"shr-buttons": "2.0.3",
|
||||||
|
"url-polyfill": "^1.1.5"
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,16 @@
|
|||||||
// Please see readme.md in the root or github.com/sampotts/plyr
|
// Please see readme.md in the root or github.com/sampotts/plyr
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import './tab-focus';
|
||||||
|
import 'custom-event-polyfill';
|
||||||
|
import 'url-polyfill';
|
||||||
|
|
||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
|
import Shr from 'shr-buttons';
|
||||||
|
|
||||||
import Plyr from '../../../src/js/plyr';
|
import Plyr from '../../../src/js/plyr';
|
||||||
|
import sources from './sources';
|
||||||
|
import toggleClass from './toggle-class';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const { host } = window.location;
|
const { host } = window.location;
|
||||||
@ -17,45 +25,15 @@ import Plyr from '../../../src/js/plyr';
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
Raven.context(() => {
|
Raven.context(() => {
|
||||||
const selector = '#player';
|
const selector = '#player';
|
||||||
const container = document.getElementById('container');
|
|
||||||
|
|
||||||
if (window.Shr) {
|
// Setup share buttons
|
||||||
window.Shr.setup('.js-shr-button', {
|
Shr.setup('.js-shr', {
|
||||||
count: {
|
count: {
|
||||||
classname: 'button__count',
|
className: 'button__count',
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
className: 'button--with-count',
|
||||||
},
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup tab focus
|
|
||||||
const tabClassName = 'tab-focus';
|
|
||||||
|
|
||||||
// Remove class on blur
|
|
||||||
document.addEventListener('focusout', event => {
|
|
||||||
if (!event.target.classList || container.contains(event.target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.target.classList.remove(tabClassName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
|
||||||
document.addEventListener('keydown', event => {
|
|
||||||
if (event.keyCode !== 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
|
||||||
// This event fires before the focusin event
|
|
||||||
setTimeout(() => {
|
|
||||||
const focused = document.activeElement;
|
|
||||||
|
|
||||||
if (!focused || !focused.classList || container.contains(focused)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
focused.classList.add(tabClassName);
|
|
||||||
}, 10);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup the player
|
// Setup the player
|
||||||
@ -93,131 +71,12 @@ import Plyr from '../../../src/js/plyr';
|
|||||||
|
|
||||||
// Setup type toggle
|
// Setup type toggle
|
||||||
const buttons = document.querySelectorAll('[data-source]');
|
const buttons = document.querySelectorAll('[data-source]');
|
||||||
const types = {
|
const types = Object.keys(sources);
|
||||||
video: 'video',
|
const historySupport = Boolean(window.history && window.history.pushState);
|
||||||
audio: 'audio',
|
let currentType = window.location.hash.substring(1);
|
||||||
youtube: 'youtube',
|
const hasCurrentType = !currentType.length;
|
||||||
vimeo: 'vimeo',
|
|
||||||
};
|
|
||||||
let currentType = window.location.hash.replace('#', '');
|
|
||||||
const historySupport = window.history && window.history.pushState;
|
|
||||||
|
|
||||||
// Toggle class on an element
|
|
||||||
function toggleClass(element, className, state) {
|
|
||||||
if (element) {
|
|
||||||
element.classList[state ? 'add' : 'remove'](className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new source
|
|
||||||
function newSource(type, init) {
|
|
||||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
|
||||||
if (
|
|
||||||
!(type in types) ||
|
|
||||||
(!init && type === currentType) ||
|
|
||||||
(!currentType.length && type === types.video)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case types.video:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 576,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 720,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 1080,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 1440,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
|
||||||
tracks: [
|
|
||||||
{
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'English',
|
|
||||||
srclang: 'en',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'French',
|
|
||||||
srclang: 'fr',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case types.audio:
|
|
||||||
player.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 types.youtube:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
|
||||||
provider: 'youtube',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case types.vimeo:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://vimeo.com/76979871',
|
|
||||||
provider: 'vimeo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the current type for next time
|
|
||||||
currentType = type;
|
|
||||||
|
|
||||||
|
function render(type) {
|
||||||
// Remove active classes
|
// Remove active classes
|
||||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
@ -226,9 +85,31 @@ import Plyr from '../../../src/js/plyr';
|
|||||||
|
|
||||||
// Show cite
|
// Show cite
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
||||||
cite.setAttribute('hidden', '');
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
cite.hidden = true;
|
||||||
});
|
});
|
||||||
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
|
|
||||||
|
document.querySelector(`.plyr__cite--${type}`).hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new source
|
||||||
|
function setSource(type, init) {
|
||||||
|
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||||
|
if (
|
||||||
|
!types.includes(type) ||
|
||||||
|
(!init && type === currentType) ||
|
||||||
|
(!currentType.length && type === 'video')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new source
|
||||||
|
player.source = sources[type];
|
||||||
|
|
||||||
|
// Set the current type for next time
|
||||||
|
currentType = type;
|
||||||
|
|
||||||
|
render(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind to each button
|
// Bind to each button
|
||||||
@ -236,7 +117,7 @@ import Plyr from '../../../src/js/plyr';
|
|||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const type = button.getAttribute('data-source');
|
const type = button.getAttribute('data-source');
|
||||||
|
|
||||||
newSource(type);
|
setSource(type);
|
||||||
|
|
||||||
if (historySupport) {
|
if (historySupport) {
|
||||||
window.history.pushState({ type }, '', `#${type}`);
|
window.history.pushState({ type }, '', `#${type}`);
|
||||||
@ -246,36 +127,27 @@ import Plyr from '../../../src/js/plyr';
|
|||||||
|
|
||||||
// List for backwards/forwards
|
// List for backwards/forwards
|
||||||
window.addEventListener('popstate', event => {
|
window.addEventListener('popstate', event => {
|
||||||
if (event.state && 'type' in event.state) {
|
if (event.state && Object.keys(event.state).includes('type')) {
|
||||||
newSource(event.state.type);
|
setSource(event.state.type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// On load
|
|
||||||
if (historySupport) {
|
|
||||||
const video = !currentType.length;
|
|
||||||
|
|
||||||
// If there's no current type set, assume video
|
// If there's no current type set, assume video
|
||||||
if (video) {
|
if (hasCurrentType) {
|
||||||
currentType = types.video;
|
currentType = 'video';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace current history state
|
// Replace current history state
|
||||||
if (currentType in types) {
|
if (historySupport && types.includes(currentType)) {
|
||||||
window.history.replaceState(
|
window.history.replaceState({ type: currentType }, '', hasCurrentType ? '' : `#${currentType}`);
|
||||||
{
|
|
||||||
type: currentType,
|
|
||||||
},
|
|
||||||
'',
|
|
||||||
video ? '' : `#${currentType}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not video, load the source
|
// If it's not video, load the source
|
||||||
if (currentType !== types.video) {
|
if (currentType !== 'video') {
|
||||||
newSource(currentType, true);
|
setSource(currentType, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(currentType);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
78
demo/src/js/sources.js
Normal file
78
demo/src/js/sources.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
const sources = {
|
||||||
|
video: {
|
||||||
|
type: 'video',
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 576,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 720,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 1080,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 1440,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
|
tracks: [
|
||||||
|
{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'English',
|
||||||
|
srclang: 'en',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'French',
|
||||||
|
srclang: 'fr',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
youtube: {
|
||||||
|
type: 'video',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
|
provider: 'youtube',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
vimeo: {
|
||||||
|
type: 'video',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://vimeo.com/76979871',
|
||||||
|
provider: 'vimeo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sources;
|
31
demo/src/js/tab-focus.js
Normal file
31
demo/src/js/tab-focus.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Setup tab focus
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
const tabClassName = 'tab-focus';
|
||||||
|
|
||||||
|
// Remove class on blur
|
||||||
|
document.addEventListener('focusout', event => {
|
||||||
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.target.classList.remove(tabClassName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add classname to tabbed elements
|
||||||
|
document.addEventListener('keydown', event => {
|
||||||
|
if (event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay the adding of classname until the focus has changed
|
||||||
|
// This event fires before the focusin event
|
||||||
|
setTimeout(() => {
|
||||||
|
const focused = document.activeElement;
|
||||||
|
|
||||||
|
if (!focused || !focused.classList || container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused.classList.add(tabClassName);
|
||||||
|
}, 10);
|
||||||
|
});
|
5
demo/src/js/toggle-class.js
Normal file
5
demo/src/js/toggle-class.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Toggle class on an element
|
||||||
|
const toggleClass = (element, className = '', toggle = false) =>
|
||||||
|
element && element.classList[toggle ? 'add' : 'remove'](className);
|
||||||
|
|
||||||
|
export default toggleClass;
|
@ -6,11 +6,9 @@
|
|||||||
.button,
|
.button,
|
||||||
.button__count {
|
.button__count {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: $color-button-background;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||||
color: $color-button-text;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: ($spacing-base * 0.75);
|
padding: ($spacing-base * 0.75);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -21,14 +19,16 @@
|
|||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.button {
|
.button {
|
||||||
|
background: $color-button-background;
|
||||||
|
color: $color-button-text;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
padding-left: $spacing-base;
|
padding-left: ($spacing-base * 1.25);
|
||||||
padding-right: $spacing-base;
|
padding-right: ($spacing-base * 1.25);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: $gray-dark;
|
background: $color-button-background-hover;
|
||||||
|
|
||||||
// Remove the underline/border
|
// Remove the underline/border
|
||||||
&::after {
|
&::after {
|
||||||
@ -38,7 +38,6 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -50,7 +49,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(1px);
|
top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +65,14 @@
|
|||||||
// Count bubble
|
// Count bubble
|
||||||
.button__count {
|
.button__count {
|
||||||
animation: fadein 0.2s ease;
|
animation: fadein 0.2s ease;
|
||||||
margin-left: ($spacing-base / 2);
|
background: $color-button-count-background;
|
||||||
|
color: $color-button-count-text;
|
||||||
|
margin-left: ($spacing-base * 0.75);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border: $arrow-size solid transparent;
|
border: $arrow-size solid transparent;
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-right-color: $color-button-background;
|
border-right-color: $color-button-count-background;
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -6,6 +6,13 @@ header {
|
|||||||
padding-bottom: $spacing-base;
|
padding-bottom: $spacing-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
h1 span {
|
||||||
|
animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: $font-weight-light;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.call-to-action {
|
.call-to-action {
|
||||||
margin-top: ($spacing-base * 1.5);
|
margin-top: ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
@ -15,5 +22,9 @@ header {
|
|||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
padding-bottom: ($spacing-base * 2);
|
padding-bottom: ($spacing-base * 2);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
p:first-of-type {
|
||||||
|
@include font-size($font-size-base + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,5 @@ label svg {
|
|||||||
|
|
||||||
a .icon,
|
a .icon,
|
||||||
.btn .icon {
|
.btn .icon {
|
||||||
margin-right: floor($spacing-base / 3);
|
margin-right: ($spacing-base / 2);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ button.faux-link {
|
|||||||
a {
|
a {
|
||||||
border-bottom: 1px dotted currentColor;
|
border-bottom: 1px dotted currentColor;
|
||||||
color: $color-link;
|
color: $color-link;
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
@ -2,16 +2,10 @@
|
|||||||
// Examples
|
// Examples
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// For non supported browsers
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example players
|
// Example players
|
||||||
.plyr {
|
.plyr {
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 2px 5px rgba(#000, 0.2);
|
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
||||||
margin: $spacing-base auto;
|
margin: $spacing-base auto;
|
||||||
|
|
||||||
&.plyr--audio {
|
&.plyr--audio {
|
||||||
@ -34,17 +28,9 @@ video {
|
|||||||
|
|
||||||
// Style full supported player
|
// Style full supported player
|
||||||
.plyr__cite {
|
.plyr__cite {
|
||||||
display: none;
|
color: $color-gray-5;
|
||||||
margin-top: $spacing-base;
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: ceil($spacing-base / 6);
|
margin-right: ceil($spacing-base / 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--video:not(.plyr--youtube):not(.plyr--vimeo) ~ ul .plyr__cite--video,
|
|
||||||
.plyr--audio ~ ul .plyr__cite--audio,
|
|
||||||
.plyr--youtube ~ ul .plyr__cite--youtube,
|
|
||||||
.plyr--vimeo ~ ul .plyr__cite--vimeo {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
@ -35,11 +35,10 @@ main {
|
|||||||
aside {
|
aside {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: $gray;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: ($spacing-base * 0.75);
|
padding: $spacing-base;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
|
@ -11,3 +11,17 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes shrinkHide {
|
||||||
|
0% {
|
||||||
|
opacity: 0.5;
|
||||||
|
width: 38px;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
@return #{$rem}rem;
|
@return #{$rem}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin font-size($size: 16) {
|
@mixin font-size($size: $font-size-base) {
|
||||||
font-size: $size * 1px; // Fallback in px
|
font-size: $size * 1px; // Fallback in px
|
||||||
font-size: calculate-rem($size);
|
font-size: calculate-rem($size);
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,41 @@
|
|||||||
// Colors
|
// Colors
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Greyscale
|
// Grayscale
|
||||||
$gray-dark: #343f4a;
|
$color-gray-9: hsl(210, 15%, 16%);
|
||||||
$gray: #55646b;
|
$color-gray-8: lighten($color-gray-9, 9%);
|
||||||
$gray-light: #cbd0d3;
|
$color-gray-7: lighten($color-gray-8, 9%);
|
||||||
$gray-lighter: #dbe3e8;
|
$color-gray-6: lighten($color-gray-7, 9%);
|
||||||
$off-white: #f2f5f7;
|
$color-gray-5: lighten($color-gray-6, 9%);
|
||||||
|
$color-gray-4: lighten($color-gray-5, 9%);
|
||||||
|
$color-gray-3: lighten($color-gray-4, 9%);
|
||||||
|
$color-gray-2: lighten($color-gray-3, 9%);
|
||||||
|
$color-gray-1: lighten($color-gray-2, 9%);
|
||||||
|
$color-gray-0: lighten($color-gray-1, 9%);
|
||||||
|
|
||||||
|
// Branding
|
||||||
|
$color-brand-primary: hsl(198, 100%, 50%);
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
$color-text: #fff;
|
$color-text: $color-gray-7;
|
||||||
|
$color-headings: $color-brand-primary;
|
||||||
// Plyr
|
|
||||||
$color-brand-primary: #1aafff;
|
|
||||||
|
|
||||||
// Brands
|
// Brands
|
||||||
$color-twitter: #4baaf4;
|
$color-twitter: #4baaf4;
|
||||||
$color-youtube: #cc181e;
|
|
||||||
$color-vimeo: #19b7ed;
|
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
$color-link: #fff;
|
$color-link: $color-brand-primary;
|
||||||
$color-background: $color-brand-primary;
|
|
||||||
|
// Background
|
||||||
|
$color-background-from: hsl(198, 100%, 94%);
|
||||||
|
$color-background-to: hsl(198, 100%, 98%);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
$color-button-background: #fff;
|
$color-button-background: $color-brand-primary;
|
||||||
$color-button-text: $gray;
|
$color-button-text: #fff;
|
||||||
|
$color-button-background-hover: hsl(198, 100%, 55%);
|
||||||
|
$color-button-count-background: #fff;
|
||||||
|
$color-button-count-text: $color-gray-6;
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
$tab-focus-default-color: #fff;
|
$tab-focus-default-color: #fff;
|
||||||
|
@ -9,4 +9,4 @@ $arrow-size: 5px;
|
|||||||
$border-radius-base: 4px;
|
$border-radius-base: 4px;
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
$page-background: linear-gradient(to left top, lighten($color-background, 10%), darken($color-background, 20%));
|
$page-background: linear-gradient(to left top, $color-background-from, $color-background-to);
|
||||||
|
@ -14,6 +14,9 @@ $plyr-font-size-badges: 9px;
|
|||||||
// Other
|
// Other
|
||||||
$plyr-font-smoothing: true;
|
$plyr-font-smoothing: true;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
$plyr-color-main: $color-brand-primary;
|
||||||
|
|
||||||
// Captions
|
// Captions
|
||||||
$plyr-font-size-captions-base: $plyr-font-size-base;
|
$plyr-font-size-captions-base: $plyr-font-size-base;
|
||||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
$plyr-font-size-captions-small: $plyr-font-size-small;
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
// Colors
|
// Colors
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$spacing-base: 20px;
|
$spacing-base: 16px;
|
||||||
|
@ -14,7 +14,6 @@ body {
|
|||||||
font-family: $font-sans-serif;
|
font-family: $font-sans-serif;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
text-shadow: 0 1px 1px rgba(#000, 0.15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@ -26,7 +25,7 @@ textarea {
|
|||||||
|
|
||||||
p,
|
p,
|
||||||
small {
|
small {
|
||||||
margin: 0 0 $spacing-base;
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@include font-size($font-size-h1);
|
@include font-size($font-size-h1);
|
||||||
|
color: $color-headings;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
letter-spacing: $letter-spacing-headings;
|
letter-spacing: $letter-spacing-headings;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
margin: 0 0 $spacing-base;
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
28
demo/yarn.lock
Normal file
28
demo/yarn.lock
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
core-js@^3.1.4:
|
||||||
|
version "3.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
|
||||||
|
integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
|
||||||
|
|
||||||
|
custom-event-polyfill@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
||||||
|
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||||
|
|
||||||
|
raven-js@^3.27.2:
|
||||||
|
version "3.27.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.27.2.tgz#6c33df952026cd73820aa999122b7b7737a66775"
|
||||||
|
integrity sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ==
|
||||||
|
|
||||||
|
shr-buttons@2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/shr-buttons/-/shr-buttons-2.0.3.tgz#2ffd021fc3d789e1510ce2736b938bd09ea1da5a"
|
||||||
|
integrity sha512-sPAgHiw4uaIt9TnxTfyZEedDChcldSVtnBHE44cpe/mSC7rqm4IEKZRLYqnVlTcGM+FSDNBPUNpSf50Q2ntd+w==
|
||||||
|
|
||||||
|
url-polyfill@^1.1.5:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.5.tgz#bec79b72b5407dba6d8cced2e32e4ab273aa9fb1"
|
||||||
|
integrity sha512-9XjIJ6nwrU+nGd8t90Ze0Zs7t8A+SU0gqsqPttj6j3zAVe5q0HFcuv37nDBdVSPpi4aTHTfbUF/i+ZVD+o2EbA==
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
1807
dist/plyr.js
vendored
1807
dist/plyr.js
vendored
File diff suppressed because it is too large
Load Diff
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
2
dist/plyr.min.mjs
vendored
2
dist/plyr.min.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.mjs.map
vendored
2
dist/plyr.min.mjs.map
vendored
File diff suppressed because one or more lines are too long
1801
dist/plyr.mjs
vendored
1801
dist/plyr.mjs
vendored
File diff suppressed because it is too large
Load Diff
9942
dist/plyr.polyfilled.js
vendored
9942
dist/plyr.polyfilled.js
vendored
File diff suppressed because it is too large
Load Diff
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
2
dist/plyr.polyfilled.min.mjs
vendored
2
dist/plyr.polyfilled.min.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.mjs.map
vendored
2
dist/plyr.polyfilled.min.mjs.map
vendored
File diff suppressed because one or more lines are too long
9936
dist/plyr.polyfilled.mjs
vendored
9936
dist/plyr.polyfilled.mjs
vendored
File diff suppressed because it is too large
Load Diff
134
gulpfile.js
134
gulpfile.js
@ -6,48 +6,57 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
|
// ------------------------------------
|
||||||
// JavaScript
|
// JavaScript
|
||||||
|
// ------------------------------------
|
||||||
const terser = require('gulp-terser');
|
const terser = require('gulp-terser');
|
||||||
const rollup = require('gulp-better-rollup');
|
const rollup = require('gulp-better-rollup');
|
||||||
const babel = require('rollup-plugin-babel');
|
const babel = require('rollup-plugin-babel');
|
||||||
const commonjs = require('rollup-plugin-commonjs');
|
const commonjs = require('rollup-plugin-commonjs');
|
||||||
const resolve = require('rollup-plugin-node-resolve');
|
const resolve = require('rollup-plugin-node-resolve');
|
||||||
|
// ------------------------------------
|
||||||
// CSS
|
// CSS
|
||||||
|
// ------------------------------------
|
||||||
const sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
const clean = require('gulp-clean-css');
|
const clean = require('gulp-clean-css');
|
||||||
const prefix = require('gulp-autoprefixer');
|
const prefix = require('gulp-autoprefixer');
|
||||||
|
// ------------------------------------
|
||||||
// Images
|
// Images
|
||||||
|
// ------------------------------------
|
||||||
const svgstore = require('gulp-svgstore');
|
const svgstore = require('gulp-svgstore');
|
||||||
const imagemin = require('gulp-imagemin');
|
const imagemin = require('gulp-imagemin');
|
||||||
|
// ------------------------------------
|
||||||
// Utils
|
// Utils
|
||||||
|
// ------------------------------------
|
||||||
const del = require('del');
|
const del = require('del');
|
||||||
const filter = require('gulp-filter');
|
const filter = require('gulp-filter');
|
||||||
const header = require('gulp-header');
|
const header = require('gulp-header');
|
||||||
const gitbranch = require('git-branch');
|
const gitbranch = require('git-branch');
|
||||||
const rename = require('gulp-rename');
|
const rename = require('gulp-rename');
|
||||||
const replace = require('gulp-replace');
|
const replace = require('gulp-replace');
|
||||||
|
const ansi = require('ansi-colors');
|
||||||
const log = require('fancy-log');
|
const log = require('fancy-log');
|
||||||
const open = require('gulp-open');
|
const open = require('gulp-open');
|
||||||
const plumber = require('gulp-plumber');
|
const plumber = require('gulp-plumber');
|
||||||
const size = require('gulp-size');
|
const size = require('gulp-size');
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
const through = require('through2');
|
const through = require('through2');
|
||||||
|
// ------------------------------------
|
||||||
// Deployment
|
// Deployment
|
||||||
|
// ------------------------------------
|
||||||
const aws = require('aws-sdk');
|
const aws = require('aws-sdk');
|
||||||
const publish = require('gulp-awspublish');
|
const publish = require('gulp-awspublish');
|
||||||
const FastlyPurge = require('fastly-purge');
|
const FastlyPurge = require('fastly-purge');
|
||||||
|
// ------------------------------------
|
||||||
|
// Configs
|
||||||
|
// ------------------------------------
|
||||||
const pkg = require('./package.json');
|
const pkg = require('./package.json');
|
||||||
const build = require('./build.json');
|
const build = require('./build.json');
|
||||||
const deploy = require('./deploy.json');
|
const deploy = require('./deploy.json');
|
||||||
|
// ------------------------------------
|
||||||
|
// Info from package
|
||||||
|
// ------------------------------------
|
||||||
const { browserslist: browsers, version } = pkg;
|
const { browserslist: browsers, version } = pkg;
|
||||||
|
|
||||||
const minSuffix = '.min';
|
const minSuffix = '.min';
|
||||||
|
|
||||||
// Get AWS config
|
// Get AWS config
|
||||||
@ -95,6 +104,7 @@ const paths = {
|
|||||||
path.join(__dirname, 'dist/*.svg'),
|
path.join(__dirname, 'dist/*.svg'),
|
||||||
path.join(__dirname, `demo/dist/*${minSuffix}.*`),
|
path.join(__dirname, `demo/dist/*${minSuffix}.*`),
|
||||||
path.join(__dirname, 'demo/dist/*.css'),
|
path.join(__dirname, 'demo/dist/*.css'),
|
||||||
|
path.join(__dirname, 'demo/dist/*.svg'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,32 +113,14 @@ const tasks = {
|
|||||||
css: [],
|
css: [],
|
||||||
js: [],
|
js: [],
|
||||||
sprite: [],
|
sprite: [],
|
||||||
clean: ['clean'],
|
clean: 'clean',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Size plugin
|
// Size plugin
|
||||||
const sizeOptions = { showFiles: true, gzip: true };
|
const sizeOptions = { showFiles: true, gzip: true };
|
||||||
|
|
||||||
// Babel config
|
|
||||||
const babelrc = (polyfill = false) => ({
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{
|
|
||||||
targets: {
|
|
||||||
browsers,
|
|
||||||
},
|
|
||||||
useBuiltIns: polyfill ? 'usage' : false,
|
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
babelrc: false,
|
|
||||||
exclude: 'node_modules/**',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean out /dist
|
// Clean out /dist
|
||||||
gulp.task('clean', done => {
|
gulp.task(tasks.clean, done => {
|
||||||
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
||||||
|
|
||||||
// Don't delete the mp4
|
// Don't delete the mp4
|
||||||
@ -139,30 +131,44 @@ gulp.task('clean', done => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
// JAvaScript
|
// JavaScript
|
||||||
|
|
||||||
const namespace = 'Plyr';
|
|
||||||
|
|
||||||
Object.entries(build.js).forEach(([filename, entry]) => {
|
Object.entries(build.js).forEach(([filename, entry]) => {
|
||||||
entry.formats.forEach(format => {
|
const { dist, formats, namespace, polyfill, src } = entry;
|
||||||
const name = `js:${filename}:${format}`;
|
|
||||||
tasks.js.push(name);
|
|
||||||
const polyfill = filename.includes('polyfilled');
|
|
||||||
const extension = format === 'es' ? 'mjs' : 'js';
|
|
||||||
|
|
||||||
gulp.task(name, () => {
|
formats.forEach(format => {
|
||||||
return gulp
|
const name = `js:${filename}:${format}`;
|
||||||
.src(entry.src)
|
const extension = format === 'es' ? 'mjs' : 'js';
|
||||||
|
tasks.js.push(name);
|
||||||
|
|
||||||
|
gulp.task(name, () =>
|
||||||
|
gulp
|
||||||
|
.src(src)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(
|
.pipe(
|
||||||
rollup(
|
rollup(
|
||||||
{
|
{
|
||||||
plugins: [resolve(), commonjs(), babel(babelrc(polyfill))],
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
babel({
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/env',
|
||||||
|
{
|
||||||
|
// debug: true,
|
||||||
|
useBuiltIns: polyfill ? 'usage' : false,
|
||||||
|
corejs: polyfill ? 3 : undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
babelrc: false,
|
||||||
|
exclude: [/\/core-js\//],
|
||||||
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: namespace,
|
name: namespace,
|
||||||
// exports: 'named',
|
|
||||||
format,
|
format,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -173,25 +179,26 @@ Object.entries(build.js).forEach(([filename, entry]) => {
|
|||||||
extname: `.${extension}`,
|
extname: `.${extension}`,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.pipe(gulp.dest(entry.dist))
|
.pipe(gulp.dest(dist))
|
||||||
.pipe(filter(`**/*${extension}`))
|
.pipe(filter(`**/*.${extension}`))
|
||||||
.pipe(terser())
|
.pipe(terser())
|
||||||
.pipe(rename({ suffix: minSuffix }))
|
.pipe(rename({ suffix: minSuffix }))
|
||||||
.pipe(size(sizeOptions))
|
.pipe(size(sizeOptions))
|
||||||
.pipe(sourcemaps.write(''))
|
.pipe(sourcemaps.write(''))
|
||||||
.pipe(gulp.dest(entry.dist));
|
.pipe(gulp.dest(dist)),
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// CSS
|
// CSS
|
||||||
Object.entries(build.css).forEach(([filename, entry]) => {
|
Object.entries(build.css).forEach(([filename, entry]) => {
|
||||||
|
const { dist, src } = entry;
|
||||||
const name = `css:${filename}`;
|
const name = `css:${filename}`;
|
||||||
tasks.css.push(name);
|
tasks.css.push(name);
|
||||||
|
|
||||||
gulp.task(name, () => {
|
gulp.task(name, () =>
|
||||||
return gulp
|
gulp
|
||||||
.src(entry.src)
|
.src(src)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(sass())
|
.pipe(sass())
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -201,27 +208,26 @@ Object.entries(build.css).forEach(([filename, entry]) => {
|
|||||||
)
|
)
|
||||||
.pipe(clean())
|
.pipe(clean())
|
||||||
.pipe(size(sizeOptions))
|
.pipe(size(sizeOptions))
|
||||||
.pipe(gulp.dest(entry.dist));
|
.pipe(gulp.dest(dist)),
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// SVG Sprites
|
// SVG Sprites
|
||||||
Object.entries(build.sprite).forEach(([filename, entry]) => {
|
Object.entries(build.sprite).forEach(([filename, entry]) => {
|
||||||
|
const { dist, src } = entry;
|
||||||
const name = `sprite:${filename}`;
|
const name = `sprite:${filename}`;
|
||||||
tasks.sprite.push(name);
|
tasks.sprite.push(name);
|
||||||
|
|
||||||
log(path.basename(filename));
|
gulp.task(name, () =>
|
||||||
|
gulp
|
||||||
gulp.task(name, () => {
|
.src(src)
|
||||||
return gulp
|
|
||||||
.src(entry.src)
|
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(imagemin())
|
.pipe(imagemin())
|
||||||
.pipe(svgstore())
|
.pipe(svgstore())
|
||||||
.pipe(rename({ basename: path.parse(filename).name }))
|
.pipe(rename({ basename: path.parse(filename).name }))
|
||||||
.pipe(size(sizeOptions))
|
.pipe(size(sizeOptions))
|
||||||
.pipe(gulp.dest(entry.dist));
|
.pipe(gulp.dest(dist)),
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build all JS
|
// Build all JS
|
||||||
@ -319,7 +325,7 @@ gulp.task('version', done => {
|
|||||||
|
|
||||||
const { domain } = deploy.cdn;
|
const { domain } = deploy.cdn;
|
||||||
|
|
||||||
console.log(`Updating versions to '${version}'...`);
|
log(`Uploading ${ansi.green.bold(version)} to ${ansi.cyan(domain)}...`);
|
||||||
|
|
||||||
// Replace versioned URLs in source
|
// Replace versioned URLs in source
|
||||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
||||||
@ -344,7 +350,7 @@ gulp.task('cdn', done => {
|
|||||||
throw new Error('No publisher instance. Check AWS configuration.');
|
throw new Error('No publisher instance. Check AWS configuration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Uploading '${version}' to ${domain}...`);
|
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
||||||
|
|
||||||
// Upload to CDN
|
// Upload to CDN
|
||||||
return (
|
return (
|
||||||
@ -387,13 +393,13 @@ gulp.task('purge', () => {
|
|||||||
const purge = new FastlyPurge(fastly.token);
|
const purge = new FastlyPurge(fastly.token);
|
||||||
|
|
||||||
list.forEach(url => {
|
list.forEach(url => {
|
||||||
console.log(`Purging ${url}...`);
|
log(`Purging ${ansi.cyan(url)}...`);
|
||||||
|
|
||||||
purge.url(url, (error, result) => {
|
purge.url(url, (error, result) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log(error);
|
log.error(error);
|
||||||
} else if (result) {
|
} else if (result) {
|
||||||
console.log(result);
|
log(result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -414,7 +420,7 @@ gulp.task('demo', done => {
|
|||||||
throw new Error('No publisher instance. Check AWS configuration.');
|
throw new Error('No publisher instance. Check AWS configuration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Uploading '${version}' demo to ${deploy.demo.domain}...`);
|
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
||||||
|
|
||||||
// Replace versioned files in readme.md
|
// Replace versioned files in readme.md
|
||||||
gulp.src([`${__dirname}/readme.md`])
|
gulp.src([`${__dirname}/readme.md`])
|
||||||
|
79
package.json
79
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.5.0-beta.5",
|
"version": "3.5.6",
|
||||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
@ -27,38 +27,37 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sampotts/plyr/issues"
|
"url": "https://github.com/sampotts/plyr/issues"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": "> 1%",
|
||||||
"> 1%",
|
|
||||||
"not dead"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"lint": "eslint src/js && npm run-script remark",
|
"lint": "eslint src/js && npm run-script remark",
|
||||||
|
"lint:fix": "eslint --fix src/js",
|
||||||
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
||||||
"deploy": "yarn lint && gulp deploy"
|
"deploy": "yarn lint && gulp deploy"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-sdk": "^2.404.0",
|
"ansi-colors": "^4.0.1",
|
||||||
"@babel/core": "^7.3.3",
|
"aws-sdk": "^2.478.0",
|
||||||
"@babel/preset-env": "^7.3.1",
|
"@babel/core": "^7.4.5",
|
||||||
"babel-eslint": "^10.0.1",
|
"@babel/preset-env": "^7.4.5",
|
||||||
"babel-preset-minify": "^0.5.0",
|
"babel-eslint": "^10.0.2",
|
||||||
"del": "^3.0.0",
|
"del": "^4.1.1",
|
||||||
"eslint": "^5.14.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-airbnb-base": "^13.1.0",
|
"eslint-config-airbnb-base": "^13.1.0",
|
||||||
"eslint-config-prettier": "^4.0.0",
|
"eslint-config-prettier": "^5.0.0",
|
||||||
"eslint-plugin-import": "^2.16.0",
|
"eslint-plugin-import": "^2.17.3",
|
||||||
|
"eslint-plugin-simple-import-sort": "^4.0.0",
|
||||||
"fancy-log": "^1.3.3",
|
"fancy-log": "^1.3.3",
|
||||||
"fastly-purge": "^1.0.1",
|
"fastly-purge": "^1.0.1",
|
||||||
"git-branch": "^2.0.1",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.2",
|
||||||
"gulp-autoprefixer": "^6.0.0",
|
"gulp-autoprefixer": "^6.1.0",
|
||||||
"gulp-awspublish": "^4.0.0",
|
"gulp-awspublish": "^4.0.0",
|
||||||
"gulp-better-rollup": "^3.4.0",
|
"gulp-better-rollup": "^4.0.1",
|
||||||
"gulp-clean-css": "^4.0.0",
|
"gulp-clean-css": "^4.2.0",
|
||||||
"gulp-filter": "^5.1.0",
|
"gulp-filter": "^6.0.0",
|
||||||
"gulp-header": "^2.0.7",
|
"gulp-header": "^2.0.7",
|
||||||
"gulp-imagemin": "^5.0.3",
|
"gulp-imagemin": "^6.0.0",
|
||||||
"gulp-open": "^3.0.1",
|
"gulp-open": "^3.0.1",
|
||||||
"gulp-plumber": "^1.2.1",
|
"gulp-plumber": "^1.2.1",
|
||||||
"gulp-postcss": "^8.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
@ -66,32 +65,32 @@
|
|||||||
"gulp-replace": "^1.0.0",
|
"gulp-replace": "^1.0.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-sourcemaps": "^2.6.4",
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
"gulp-svgstore": "^7.0.1",
|
"gulp-svgstore": "^7.0.1",
|
||||||
"gulp-terser": "^1.1.7",
|
"gulp-terser": "^1.2.0",
|
||||||
"postcss-custom-properties": "^8.0.9",
|
"postcss-custom-properties": "^9.0.1",
|
||||||
"prettier-eslint": "^8.8.2",
|
"prettier-eslint": "^9.0.0",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"remark-cli": "^6.0.1",
|
"remark-cli": "^6.0.1",
|
||||||
"remark-validate-links": "^8.0.0",
|
"remark-validate-links": "^8.0.3",
|
||||||
|
"rollup": "^1.15.6",
|
||||||
"rollup-plugin-babel": "^4.3.2",
|
"rollup-plugin-babel": "^4.3.2",
|
||||||
"rollup-plugin-commonjs": "^9.2.0",
|
"rollup-plugin-commonjs": "^10.0.0",
|
||||||
"rollup-plugin-node-resolve": "^4.0.0",
|
"rollup-plugin-node-resolve": "^5.0.3",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^10.1.0",
|
||||||
"stylelint-config-prettier": "^4.0.0",
|
"stylelint-config-prettier": "^5.2.0",
|
||||||
"stylelint-config-recommended": "^2.1.0",
|
"stylelint-config-recommended": "^2.2.0",
|
||||||
"stylelint-config-sass-guidelines": "^5.3.0",
|
"stylelint-config-sass-guidelines": "^6.0.0",
|
||||||
"stylelint-order": "^2.0.0",
|
"stylelint-order": "^3.0.0",
|
||||||
"stylelint-scss": "^3.5.3",
|
"stylelint-scss": "^3.8.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
"stylelint-selector-bem-pattern": "^2.1.0",
|
||||||
"through2": "^3.0.0"
|
"through2": "^3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^3.1.4",
|
||||||
"custom-event-polyfill": "^1.0.6",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"loadjs": "^3.5.5",
|
"loadjs": "^3.6.1",
|
||||||
"rangetouch": "^2.0.0",
|
"rangetouch": "^2.0.0",
|
||||||
"raven-js": "^3.27.0",
|
"url-polyfill": "^1.1.5"
|
||||||
"url-polyfill": "^1.1.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,30 +5,25 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
// Exclude from the editor
|
|
||||||
"files.exclude": {
|
|
||||||
"**/node_modules": true
|
|
||||||
},
|
|
||||||
// Exclude from search
|
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"dist/": true,
|
"**/node_modules": true,
|
||||||
"demo/dist/": true
|
"**/dist": true
|
||||||
},
|
},
|
||||||
// Linting
|
// Linting
|
||||||
"stylelint.enable": true,
|
"stylelint.enable": true,
|
||||||
"css.validate": false,
|
"css.validate": false,
|
||||||
"scss.validate": false,
|
"scss.validate": false,
|
||||||
"javascript.validate.enable": false,
|
"javascript.validate.enable": false,
|
||||||
|
|
||||||
// Prettier
|
// Prettier
|
||||||
"prettier.eslintIntegration": true,
|
"prettier.eslintIntegration": true,
|
||||||
"prettier.stylelintIntegration": true,
|
"prettier.stylelintIntegration": true,
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
"editor.tabSize": 4,
|
"editor.tabSize": 4,
|
||||||
"editor.insertSpaces": true,
|
"editor.insertSpaces": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": true
|
|
||||||
},
|
|
||||||
// Trim on save
|
// Trim on save
|
||||||
"files.trimTrailingWhitespace": true
|
"files.trimTrailingWhitespace": true
|
||||||
}
|
}
|
||||||
|
63
readme.md
63
readme.md
@ -1,18 +1,17 @@
|
|||||||
Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
||||||
|
|
||||||
[Checkout the demo](https://plyr.io) - [Donate](#donate) - [Slack](https://bit.ly/plyr-chat) - [](https://badge.fury.io/js/plyr)
|
[Checkout the demo](https://plyr.io) - [Donate](#donate) - [Slack](https://bit.ly/plyr--chat) - [](https://badge.fury.io/js/plyr)
|
||||||
|
|
||||||
[](https://plyr.io)
|
[](https://plyr.io)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
- 📼 **HTML Video & Audio, YouTube & Vimeo** - support for the major formats
|
||||||
- 💪 **Accessible** - full support for VTT captions and screen readers
|
- 💪 **Accessible** - full support for VTT captions and screen readers
|
||||||
- 🔧 **[Customisable](#html)** - make the player look how you want with the markup you want
|
- 🔧 **[Customizable](#html)** - make the player look how you want with the markup you want
|
||||||
- 😎 **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
- 😎 **Clean HTML** - 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
|
`<span>` or `<a href="#">` button hacks
|
||||||
- 📱 **Responsive** - works with any screen size
|
- 📱 **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
|
- 💵 **[Monetization](#ads)** - make money from your videos
|
||||||
- 📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
|
- 📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
|
||||||
- 🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
- 🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||||
@ -25,7 +24,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
|
|||||||
- 📖 **Multiple captions** - support for multiple caption tracks
|
- 📖 **Multiple captions** - support for multiple caption tracks
|
||||||
- 🌎 **i18n support** - support for internationalization of controls
|
- 🌎 **i18n support** - support for internationalization of controls
|
||||||
- 👌 **[Preview thumbnails](#preview-thumbnails)** - support for displaying preview thumbnails
|
- 👌 **[Preview thumbnails](#preview-thumbnails)** - support for displaying preview thumbnails
|
||||||
- 🤟 **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
- 🤟 **No frameworks** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||||
- 💁♀️ **SASS** - to include in your build processes
|
- 💁♀️ **SASS** - to include in your build processes
|
||||||
|
|
||||||
### Demos
|
### Demos
|
||||||
@ -109,7 +108,15 @@ Or the `<div>` non progressively enhanced method:
|
|||||||
|
|
||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
Include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
You can use Plyr as an ES6 module as follows:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import Plyr from 'plyr';
|
||||||
|
|
||||||
|
const player = new Plyr('#player');
|
||||||
|
```
|
||||||
|
|
||||||
|
Alertnatively you can include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="path/to/plyr.js"></script>
|
<script src="path/to/plyr.js"></script>
|
||||||
@ -123,18 +130,18 @@ See [initialising](#initialising) for more information on advanced setups.
|
|||||||
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.5.0-beta.5/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.5.6/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
...or...
|
...or...
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.5.0-beta.5/plyr.polyfilled.js"></script>
|
<script src="https://cdn.plyr.io/3.5.6/plyr.polyfilled.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## CSS
|
## CSS
|
||||||
|
|
||||||
Include the `plyr.css` stylsheet into your `<head>`
|
Include the `plyr.css` stylsheet into your `<head>`.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="path/to/plyr.css" />
|
<link rel="stylesheet" href="path/to/plyr.css" />
|
||||||
@ -143,13 +150,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:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.0-beta.5/plyr.css" />
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.6/plyr.css" />
|
||||||
```
|
```
|
||||||
|
|
||||||
## SVG Sprite
|
## 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
|
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.5.0-beta.5/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.6/plyr.svg`.
|
||||||
|
|
||||||
# Ads
|
# Ads
|
||||||
|
|
||||||
@ -204,7 +211,7 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
|||||||
|
|
||||||
You can specify a range of arguments for the constructor to use:
|
You can specify a range of arguments for the constructor to use:
|
||||||
|
|
||||||
- A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
- A [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
|
||||||
- A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
- A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||||
- A [jQuery](https://jquery.com) object
|
- A [jQuery](https://jquery.com) object
|
||||||
|
|
||||||
@ -212,7 +219,7 @@ _Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element
|
|||||||
|
|
||||||
#### Single player
|
#### Single player
|
||||||
|
|
||||||
Passing a [string selector](https://developer.mozilla.org/en-US/docs/Web/API/NodeList):
|
Passing a CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector):
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const player = new Plyr('#player');
|
const player = new Plyr('#player');
|
||||||
@ -238,7 +245,7 @@ You have two choices here. You can either use a simple array loop to map the con
|
|||||||
const players = Array.from(document.querySelectorAll('.js-player')).map(p => new Plyr(p));
|
const players = Array.from(document.querySelectorAll('.js-player')).map(p => new Plyr(p));
|
||||||
```
|
```
|
||||||
|
|
||||||
...or use a static method where you can pass a [string selector](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of elements, or a [JQuery](https://jquery.com) object:
|
...or use a static method where you can pass a [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement), or a [JQuery](https://jquery.com) object:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const players = Plyr.setup('.js-player');
|
const players = Plyr.setup('.js-player');
|
||||||
@ -269,13 +276,13 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
||||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||||
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
||||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
|
||||||
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||||
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
||||||
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
||||||
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||||
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `autoplay`² | Boolean | `false` | Autoplay the media on load. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
||||||
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
||||||
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
||||||
@ -293,10 +300,10 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `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. |
|
| `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: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `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. |
|
| `ratio` | String | `null` | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `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. |
|
| `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. |
|
| `quality` | Object | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }` | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources. |
|
||||||
| `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. |
|
| `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 advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. |
|
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. |
|
||||||
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
||||||
@ -305,6 +312,11 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `previewThumbnails` | Object | `{ enabled: false, src: '' }` | `enabled`: Whether to enable the preview thumbnails (they must be generated by you). `src` must be either a string or an array of strings representing URLs for the VTT files containing the image URL(s). Learn more about [preview thumbnails](#preview-thumbnails) below. |
|
| `previewThumbnails` | Object | `{ enabled: false, src: '' }` | `enabled`: Whether to enable the preview thumbnails (they must be generated by you). `src` must be either a string or an array of strings representing URLs for the VTT files containing the image URL(s). Learn more about [preview thumbnails](#preview-thumbnails) below. |
|
||||||
|
|
||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
|
2. Autoplay is generally not recommended as it is seen as a negative user experience. It is also disabled in many browsers. Before raising issues, do your homework. More info can be found here:
|
||||||
|
|
||||||
|
- https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||||
|
- https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
|
||||||
|
- https://hacks.mozilla.org/2019/02/firefox-66-to-block-automatically-playing-audible-video-and-audio/
|
||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
@ -404,10 +416,11 @@ player.fullscreen.active; // false;
|
|||||||
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
|
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
|
||||||
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
||||||
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
||||||
| `pip`² | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. |
|
| `pip`¹ | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. |
|
||||||
|
| `ratio` | ✓ | ✓ | Gets or sets the video aspect ratio. The setter accepts a string in the same format as the `ratio` option. |
|
||||||
|
| `download` | ✓ | ✓ | Gets or sets the URL for the download button. The setter accepts a string containing a valid absolute URL. |
|
||||||
|
|
||||||
1. YouTube only. HTML5 will follow.
|
1. HTML5 only
|
||||||
2. HTML5 only
|
|
||||||
|
|
||||||
### The `.source` setter
|
### The `.source` setter
|
||||||
|
|
||||||
@ -649,7 +662,7 @@ The arguments are:
|
|||||||
- Provider (`html5`, `youtube` or `vimeo`)
|
- Provider (`html5`, `youtube` or `vimeo`)
|
||||||
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||||
|
|
||||||
## Disable support programatically
|
## Disable support programmatically
|
||||||
|
|
||||||
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
||||||
|
|
||||||
@ -673,6 +686,7 @@ Some awesome folks have made plugins for CMSs and Components for JavaScript fram
|
|||||||
| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
|
| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
|
||||||
| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
|
| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
|
||||||
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
||||||
|
| REDAXO | FriendsOfRedaxo / skerbis ([@skerbis](https://friendsofredaxo.github.io)) | [https://github.com/FriendsOfREDAXO/plyr](https://github.com/FriendsOfREDAXO/plyr) |
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
@ -697,6 +711,7 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
|
|||||||
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||||
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||||
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||||
|
- [Front End Focus #177](https://frontendfoc.us/issues/177)
|
||||||
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||||
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||||
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||||
@ -716,7 +731,7 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
|
|||||||
- [Sparkk TV](https://www.sparkktv.com/)
|
- [Sparkk TV](https://www.sparkktv.com/)
|
||||||
- [@halfhalftravel](https://www.halfhalftravel.com/)
|
- [@halfhalftravel](https://www.halfhalftravel.com/)
|
||||||
|
|
||||||
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
If you want to be added to the list, open a pull request. It'd be awesome to see how you're using Plyr 😎
|
||||||
|
|
||||||
# Useful links and credits
|
# Useful links and credits
|
||||||
|
|
||||||
|
@ -85,7 +85,6 @@ const captions = {
|
|||||||
|
|
||||||
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
||||||
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
||||||
|
|
||||||
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
||||||
|
|
||||||
// Use first browser language when language is 'auto'
|
// Use first browser language when language is 'auto'
|
||||||
@ -124,7 +123,9 @@ const captions = {
|
|||||||
|
|
||||||
// Handle tracks (add event listener and "pseudo"-default)
|
// Handle tracks (add event listener and "pseudo"-default)
|
||||||
if (this.isHTML5 && this.isVideo) {
|
if (this.isHTML5 && this.isVideo) {
|
||||||
tracks.filter(track => !meta.get(track)).forEach(track => {
|
tracks
|
||||||
|
.filter(track => !meta.get(track))
|
||||||
|
.forEach(track => {
|
||||||
this.debug.log('Track added', track);
|
this.debug.log('Track added', track);
|
||||||
// Attempt to store if the original dom element was "default"
|
// Attempt to store if the original dom element was "default"
|
||||||
meta.set(track, {
|
meta.set(track, {
|
||||||
@ -132,6 +133,7 @@ const captions = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Turn off native caption rendering to avoid double captions
|
// Turn off native caption rendering to avoid double captions
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
track.mode = 'hidden';
|
track.mode = 'hidden';
|
||||||
|
|
||||||
// Add event listener for cue changes
|
// Add event listener for cue changes
|
||||||
@ -164,7 +166,6 @@ const captions = {
|
|||||||
|
|
||||||
const { toggled } = this.captions; // Current state
|
const { toggled } = this.captions; // Current state
|
||||||
const activeClass = this.config.classNames.captions.active;
|
const activeClass = this.config.classNames.captions.active;
|
||||||
|
|
||||||
// Get the next state
|
// Get the next state
|
||||||
// If the method is called without parameter, toggle based on current value
|
// If the method is called without parameter, toggle based on current value
|
||||||
const active = is.nullOrUndefined(input) ? !toggled : input;
|
const active = is.nullOrUndefined(input) ? !toggled : input;
|
||||||
@ -300,10 +301,12 @@ const captions = {
|
|||||||
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
||||||
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
||||||
let track;
|
let track;
|
||||||
|
|
||||||
languages.every(language => {
|
languages.every(language => {
|
||||||
track = sorted.find(track => track.language === language);
|
track = sorted.find(t => t.language === language);
|
||||||
return !track; // Break iteration if there is a match
|
return !track; // Break iteration if there is a match
|
||||||
});
|
});
|
||||||
|
|
||||||
// If no match is found but is required, get first
|
// If no match is found but is required, get first
|
||||||
return track || (force ? sorted[0] : undefined);
|
return track || (force ? sorted[0] : undefined);
|
||||||
},
|
},
|
||||||
@ -360,6 +363,7 @@ const captions = {
|
|||||||
// Get cues from track
|
// Get cues from track
|
||||||
if (!cues) {
|
if (!cues) {
|
||||||
const track = captions.getCurrentTrack.call(this);
|
const track = captions.getCurrentTrack.call(this);
|
||||||
|
|
||||||
cues = Array.from((track || {}).activeCues || [])
|
cues = Array.from((track || {}).activeCues || [])
|
||||||
.map(cue => cue.getCueAsHTML())
|
.map(cue => cue.getCueAsHTML())
|
||||||
.map(getHTML);
|
.map(getHTML);
|
||||||
|
@ -42,8 +42,9 @@ const defaults = {
|
|||||||
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
||||||
toggleInvert: true,
|
toggleInvert: true,
|
||||||
|
|
||||||
// Aspect ratio (for embeds)
|
// Force an aspect ratio
|
||||||
ratio: '16:9',
|
// The format must be `'w:h'` (e.g. `'16:9'`)
|
||||||
|
ratio: null,
|
||||||
|
|
||||||
// Click video container to play/pause
|
// Click video container to play/pause
|
||||||
clickToPlay: true,
|
clickToPlay: true,
|
||||||
@ -60,7 +61,7 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.0-beta.5/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -127,6 +128,7 @@ const defaults = {
|
|||||||
// 'fast-forward',
|
// 'fast-forward',
|
||||||
'progress',
|
'progress',
|
||||||
'current-time',
|
'current-time',
|
||||||
|
// 'duration',
|
||||||
'mute',
|
'mute',
|
||||||
'volume',
|
'volume',
|
||||||
'captions',
|
'captions',
|
||||||
@ -194,8 +196,7 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
youtube: {
|
youtube: {
|
||||||
sdk: 'https://www.youtube.com/iframe_api',
|
sdk: 'https://www.youtube.com/iframe_api',
|
||||||
api:
|
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
||||||
'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet',
|
|
||||||
},
|
},
|
||||||
googleIMA: {
|
googleIMA: {
|
||||||
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||||
@ -319,9 +320,6 @@ const defaults = {
|
|||||||
progress: '.plyr__progress',
|
progress: '.plyr__progress',
|
||||||
captions: '.plyr__captions',
|
captions: '.plyr__captions',
|
||||||
caption: '.plyr__caption',
|
caption: '.plyr__caption',
|
||||||
menu: {
|
|
||||||
quality: '.js-plyr__menu__list--quality',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Class hooks added to the player in different states
|
// Class hooks added to the player in different states
|
||||||
@ -330,6 +328,7 @@ const defaults = {
|
|||||||
provider: 'plyr--{0}',
|
provider: 'plyr--{0}',
|
||||||
video: 'plyr__video-wrapper',
|
video: 'plyr__video-wrapper',
|
||||||
embed: 'plyr__video-embed',
|
embed: 'plyr__video-embed',
|
||||||
|
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
|
||||||
embedContainer: 'plyr__video-embed__container',
|
embedContainer: 'plyr__video-embed__container',
|
||||||
poster: 'plyr__poster',
|
poster: 'plyr__poster',
|
||||||
posterEnabled: 'plyr__poster-enabled',
|
posterEnabled: 'plyr__poster-enabled',
|
||||||
@ -394,11 +393,6 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// API keys
|
|
||||||
keys: {
|
|
||||||
google: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Advertisements plugin
|
// Advertisements plugin
|
||||||
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
ads: {
|
ads: {
|
||||||
|
214
src/js/controls.js
vendored
214
src/js/controls.js
vendored
@ -4,6 +4,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import RangeTouch from 'rangetouch';
|
import RangeTouch from 'rangetouch';
|
||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import html5 from './html5';
|
import html5 from './html5';
|
||||||
import support from './support';
|
import support from './support';
|
||||||
@ -27,7 +28,7 @@ import {
|
|||||||
import { off, on } from './utils/events';
|
import { off, on } from './utils/events';
|
||||||
import i18n from './utils/i18n';
|
import i18n from './utils/i18n';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import loadSprite from './utils/loadSprite';
|
import loadSprite from './utils/load-sprite';
|
||||||
import { extend } from './utils/objects';
|
import { extend } from './utils/objects';
|
||||||
import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
|
import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
|
||||||
import { formatTime, getHours } from './utils/time';
|
import { formatTime, getHours } from './utils/time';
|
||||||
@ -105,7 +106,6 @@ const controls = {
|
|||||||
const namespace = 'http://www.w3.org/2000/svg';
|
const namespace = 'http://www.w3.org/2000/svg';
|
||||||
const iconUrl = controls.getIconUrl.call(this);
|
const iconUrl = controls.getIconUrl.call(this);
|
||||||
const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`;
|
const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`;
|
||||||
|
|
||||||
// Create <svg>
|
// Create <svg>
|
||||||
const icon = document.createElementNS(namespace, 'svg');
|
const icon = document.createElementNS(namespace, 'svg');
|
||||||
setAttributes(
|
setAttributes(
|
||||||
@ -172,7 +172,7 @@ const controls = {
|
|||||||
|
|
||||||
// Create a <button>
|
// Create a <button>
|
||||||
createButton(buttonType, attr) {
|
createButton(buttonType, attr) {
|
||||||
const attributes = Object.assign({}, attr);
|
const attributes = extend({}, attr);
|
||||||
let type = toCamelCase(buttonType);
|
let type = toCamelCase(buttonType);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
@ -198,8 +198,10 @@ const controls = {
|
|||||||
|
|
||||||
// Set class name
|
// Set class name
|
||||||
if (Object.keys(attributes).includes('class')) {
|
if (Object.keys(attributes).includes('class')) {
|
||||||
if (!attributes.class.includes(this.config.classNames.control)) {
|
if (!attributes.class.split(' ').some(c => c === this.config.classNames.control)) {
|
||||||
attributes.class += ` ${this.config.classNames.control}`;
|
extend(attributes, {
|
||||||
|
class: `${attributes.class} ${this.config.classNames.control}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
attributes.class = this.config.classNames.control;
|
attributes.class = this.config.classNames.control;
|
||||||
@ -377,13 +379,13 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Create time display
|
// Create time display
|
||||||
createTime(type) {
|
createTime(type, attrs) {
|
||||||
const attributes = getAttributesFromSelector(this.config.selectors.display[type]);
|
const attributes = getAttributesFromSelector(this.config.selectors.display[type], attrs);
|
||||||
|
|
||||||
const container = createElement(
|
const container = createElement(
|
||||||
'div',
|
'div',
|
||||||
extend(attributes, {
|
extend(attributes, {
|
||||||
class: `${this.config.classNames.display.time} ${attributes.class ? attributes.class : ''}`.trim(),
|
class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),
|
||||||
'aria-label': i18n.get(type, this.config),
|
'aria-label': i18n.get(type, this.config),
|
||||||
}),
|
}),
|
||||||
'00:00',
|
'00:00',
|
||||||
@ -491,15 +493,15 @@ const controls = {
|
|||||||
get() {
|
get() {
|
||||||
return menuItem.getAttribute('aria-checked') === 'true';
|
return menuItem.getAttribute('aria-checked') === 'true';
|
||||||
},
|
},
|
||||||
set(checked) {
|
set(check) {
|
||||||
// Ensure exclusivity
|
// Ensure exclusivity
|
||||||
if (checked) {
|
if (check) {
|
||||||
Array.from(menuItem.parentNode.children)
|
Array.from(menuItem.parentNode.children)
|
||||||
.filter(node => matches(node, '[role="menuitemradio"]'))
|
.filter(node => matches(node, '[role="menuitemradio"]'))
|
||||||
.forEach(node => node.setAttribute('aria-checked', 'false'));
|
.forEach(node => node.setAttribute('aria-checked', 'false'));
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
|
menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -607,17 +609,17 @@ const controls = {
|
|||||||
let value = 0;
|
let value = 0;
|
||||||
|
|
||||||
const setProgress = (target, input) => {
|
const setProgress = (target, input) => {
|
||||||
const value = is.number(input) ? input : 0;
|
const val = is.number(input) ? input : 0;
|
||||||
const progress = is.element(target) ? target : this.elements.display.buffer;
|
const progress = is.element(target) ? target : this.elements.display.buffer;
|
||||||
|
|
||||||
// Update value and label
|
// Update value and label
|
||||||
if (is.element(progress)) {
|
if (is.element(progress)) {
|
||||||
progress.value = value;
|
progress.value = val;
|
||||||
|
|
||||||
// Update text label inside
|
// Update text label inside
|
||||||
const label = progress.getElementsByTagName('span')[0];
|
const label = progress.getElementsByTagName('span')[0];
|
||||||
if (is.element(label)) {
|
if (is.element(label)) {
|
||||||
label.childNodes[0].nodeValue = value;
|
label.childNodes[0].nodeValue = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -699,14 +701,8 @@ const controls = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate percentage
|
|
||||||
let percent = 0;
|
|
||||||
const clientRect = this.elements.progress.getBoundingClientRect();
|
|
||||||
const visible = `${this.config.classNames.tooltip}--visible`;
|
const visible = `${this.config.classNames.tooltip}--visible`;
|
||||||
|
const toggle = show => toggleClass(this.elements.display.seekTooltip, visible, show);
|
||||||
const toggle = toggle => {
|
|
||||||
toggleClass(this.elements.display.seekTooltip, visible, toggle);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hide on touch
|
// Hide on touch
|
||||||
if (this.touch) {
|
if (this.touch) {
|
||||||
@ -715,6 +711,9 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine percentage, if already visible
|
// Determine percentage, if already visible
|
||||||
|
let percent = 0;
|
||||||
|
const clientRect = this.elements.progress.getBoundingClientRect();
|
||||||
|
|
||||||
if (is.event(event)) {
|
if (is.event(event)) {
|
||||||
percent = (100 / clientRect.width) * (event.pageX - clientRect.left);
|
percent = (100 / clientRect.width) * (event.pageX - clientRect.left);
|
||||||
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
|
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
|
||||||
@ -1111,7 +1110,7 @@ const controls = {
|
|||||||
let target = pane;
|
let target = pane;
|
||||||
|
|
||||||
if (!is.element(target)) {
|
if (!is.element(target)) {
|
||||||
target = Object.values(this.elements.settings.panels).find(pane => !pane.hidden);
|
target = Object.values(this.elements.settings.panels).find(p => !p.hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstItem = target.querySelector('[role^="menuitem"]');
|
const firstItem = target.querySelector('[role^="menuitem"]');
|
||||||
@ -1138,7 +1137,10 @@ const controls = {
|
|||||||
} else if (is.keyboardEvent(input) && input.which === 27) {
|
} else if (is.keyboardEvent(input) && input.which === 27) {
|
||||||
show = false;
|
show = false;
|
||||||
} else if (is.event(input)) {
|
} else if (is.event(input)) {
|
||||||
const isMenuItem = popup.contains(input.target);
|
// If Plyr is in a shadowDOM, the event target is set to the component, instead of the
|
||||||
|
// Element in the shadowDOM. The path, if available, is complete.
|
||||||
|
const target = is.function(input.composedPath) ? input.composedPath()[0] : input.target;
|
||||||
|
const isMenuItem = popup.contains(target);
|
||||||
|
|
||||||
// If the click was inside the menu or if the click
|
// If the click was inside the menu or if the click
|
||||||
// wasn't the button or menu item and we're trying to
|
// wasn't the button or menu item and we're trying to
|
||||||
@ -1191,7 +1193,7 @@ const controls = {
|
|||||||
|
|
||||||
// Show a panel in the menu
|
// Show a panel in the menu
|
||||||
showMenuPanel(type = '', tabFocus = false) {
|
showMenuPanel(type = '', tabFocus = false) {
|
||||||
const target = document.getElementById(`plyr-settings-${this.id}-${type}`);
|
const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);
|
||||||
|
|
||||||
// Nothing to show, bail
|
// Nothing to show, bail
|
||||||
if (!is.element(target)) {
|
if (!is.element(target)) {
|
||||||
@ -1244,8 +1246,8 @@ const controls = {
|
|||||||
controls.focusFirstMenuItem.call(this, target, tabFocus);
|
controls.focusFirstMenuItem.call(this, target, tabFocus);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the download link
|
// Set the download URL
|
||||||
setDownloadLink() {
|
setDownloadUrl() {
|
||||||
const button = this.elements.buttons.download;
|
const button = this.elements.buttons.download;
|
||||||
|
|
||||||
// Bail if no button
|
// Bail if no button
|
||||||
@ -1253,49 +1255,75 @@ const controls = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set download link
|
// Set attribute
|
||||||
button.setAttribute('href', this.download);
|
button.setAttribute('href', this.download);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Build the default HTML
|
// Build the default HTML
|
||||||
// TODO: Set order based on order in the config.controls array?
|
|
||||||
create(data) {
|
create(data) {
|
||||||
|
const {
|
||||||
|
bindMenuItemShortcuts,
|
||||||
|
createButton,
|
||||||
|
createProgress,
|
||||||
|
createRange,
|
||||||
|
createTime,
|
||||||
|
setQualityMenu,
|
||||||
|
setSpeedMenu,
|
||||||
|
showMenuPanel,
|
||||||
|
} = controls;
|
||||||
|
this.elements.controls = null;
|
||||||
|
|
||||||
|
// Larger overlaid play button
|
||||||
|
if (this.config.controls.includes('play-large')) {
|
||||||
|
this.elements.container.appendChild(createButton.call(this, 'play-large'));
|
||||||
|
}
|
||||||
|
|
||||||
// Create the container
|
// Create the container
|
||||||
const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
|
const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
|
||||||
|
this.elements.controls = container;
|
||||||
|
|
||||||
|
// Default item attributes
|
||||||
|
const defaultAttributes = { class: 'plyr__controls__item' };
|
||||||
|
|
||||||
|
// Loop through controls in order
|
||||||
|
dedupe(this.config.controls).forEach(control => {
|
||||||
// Restart button
|
// Restart button
|
||||||
if (this.config.controls.includes('restart')) {
|
if (control === 'restart') {
|
||||||
container.appendChild(controls.createButton.call(this, 'restart'));
|
container.appendChild(createButton.call(this, 'restart', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewind button
|
// Rewind button
|
||||||
if (this.config.controls.includes('rewind')) {
|
if (control === 'rewind') {
|
||||||
container.appendChild(controls.createButton.call(this, 'rewind'));
|
container.appendChild(createButton.call(this, 'rewind', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play/Pause button
|
// Play/Pause button
|
||||||
if (this.config.controls.includes('play')) {
|
if (control === 'play') {
|
||||||
container.appendChild(controls.createButton.call(this, 'play'));
|
container.appendChild(createButton.call(this, 'play', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast forward button
|
// Fast forward button
|
||||||
if (this.config.controls.includes('fast-forward')) {
|
if (control === 'fast-forward') {
|
||||||
container.appendChild(controls.createButton.call(this, 'fast-forward'));
|
container.appendChild(createButton.call(this, 'fast-forward', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
if (this.config.controls.includes('progress')) {
|
if (control === 'progress') {
|
||||||
|
const progressContainer = createElement('div', {
|
||||||
|
class: `${defaultAttributes.class} plyr__progress__container`,
|
||||||
|
});
|
||||||
|
|
||||||
const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
|
const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
|
||||||
|
|
||||||
// Seek range slider
|
// Seek range slider
|
||||||
progress.appendChild(
|
progress.appendChild(
|
||||||
controls.createRange.call(this, 'seek', {
|
createRange.call(this, 'seek', {
|
||||||
id: `plyr-seek-${data.id}`,
|
id: `plyr-seek-${data.id}`,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Buffer progress
|
// Buffer progress
|
||||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
progress.appendChild(createProgress.call(this, 'buffer'));
|
||||||
|
|
||||||
// TODO: Add loop display indicator
|
// TODO: Add loop display indicator
|
||||||
|
|
||||||
@ -1314,32 +1342,45 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.elements.progress = progress;
|
this.elements.progress = progress;
|
||||||
container.appendChild(this.elements.progress);
|
progressContainer.appendChild(this.elements.progress);
|
||||||
|
container.appendChild(progressContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media current time display
|
// Media current time display
|
||||||
if (this.config.controls.includes('current-time')) {
|
if (control === 'current-time') {
|
||||||
container.appendChild(controls.createTime.call(this, 'currentTime'));
|
container.appendChild(createTime.call(this, 'currentTime', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media duration display
|
// Media duration display
|
||||||
if (this.config.controls.includes('duration')) {
|
if (control === 'duration') {
|
||||||
container.appendChild(controls.createTime.call(this, 'duration'));
|
container.appendChild(createTime.call(this, 'duration', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume controls
|
// Volume controls
|
||||||
if (this.config.controls.includes('mute') || this.config.controls.includes('volume')) {
|
if (control === 'mute' || control === 'volume') {
|
||||||
const volume = createElement('div', {
|
let { volume } = this.elements;
|
||||||
class: 'plyr__volume',
|
|
||||||
});
|
// Create the volume container if needed
|
||||||
|
if (!is.element(volume) || !container.contains(volume)) {
|
||||||
|
volume = createElement(
|
||||||
|
'div',
|
||||||
|
extend({}, defaultAttributes, {
|
||||||
|
class: `${defaultAttributes.class} plyr__volume`.trim(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.elements.volume = volume;
|
||||||
|
|
||||||
|
container.appendChild(volume);
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle mute button
|
// Toggle mute button
|
||||||
if (this.config.controls.includes('mute')) {
|
if (control === 'mute') {
|
||||||
volume.appendChild(controls.createButton.call(this, 'mute'));
|
volume.appendChild(createButton.call(this, 'mute'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume range control
|
// Volume range control
|
||||||
if (this.config.controls.includes('volume')) {
|
if (control === 'volume') {
|
||||||
// Set the attributes
|
// Set the attributes
|
||||||
const attributes = {
|
const attributes = {
|
||||||
max: 1,
|
max: 1,
|
||||||
@ -1349,7 +1390,7 @@ const controls = {
|
|||||||
|
|
||||||
// Create the volume range slider
|
// Create the volume range slider
|
||||||
volume.appendChild(
|
volume.appendChild(
|
||||||
controls.createRange.call(
|
createRange.call(
|
||||||
this,
|
this,
|
||||||
'volume',
|
'volume',
|
||||||
extend(attributes, {
|
extend(attributes, {
|
||||||
@ -1357,27 +1398,26 @@ const controls = {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.elements.volume = volume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle captions button
|
// Toggle captions button
|
||||||
if (this.config.controls.includes('captions')) {
|
if (control === 'captions') {
|
||||||
container.appendChild(controls.createButton.call(this, 'captions'));
|
container.appendChild(createButton.call(this, 'captions', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings button / menu
|
// Settings button / menu
|
||||||
if (this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
|
if (control === 'settings' && !is.empty(this.config.settings)) {
|
||||||
const control = createElement('div', {
|
const wrapper = createElement(
|
||||||
class: 'plyr__menu',
|
'div',
|
||||||
|
extend({}, defaultAttributes, {
|
||||||
|
class: `${defaultAttributes.class} plyr__menu`.trim(),
|
||||||
hidden: '',
|
hidden: '',
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
control.appendChild(
|
wrapper.appendChild(
|
||||||
controls.createButton.call(this, 'settings', {
|
createButton.call(this, 'settings', {
|
||||||
'aria-haspopup': true,
|
'aria-haspopup': true,
|
||||||
'aria-controls': `plyr-settings-${data.id}`,
|
'aria-controls': `plyr-settings-${data.id}`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
@ -1420,11 +1460,11 @@ const controls = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Bind menu shortcuts for keyboard users
|
// Bind menu shortcuts for keyboard users
|
||||||
controls.bindMenuItemShortcuts.call(this, menuItem, type);
|
bindMenuItemShortcuts.call(this, menuItem, type);
|
||||||
|
|
||||||
// Show menu on click
|
// Show menu on click
|
||||||
on(menuItem, 'click', () => {
|
on(menuItem, 'click', () => {
|
||||||
controls.showMenuPanel.call(this, type, false);
|
showMenuPanel.call(this, type, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const flex = createElement('span', null, i18n.get(type, this.config));
|
const flex = createElement('span', null, i18n.get(type, this.config));
|
||||||
@ -1489,14 +1529,14 @@ const controls = {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
// Show the respective menu
|
// Show the respective menu
|
||||||
controls.showMenuPanel.call(this, 'home', true);
|
showMenuPanel.call(this, 'home', true);
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Go back via button click
|
// Go back via button click
|
||||||
on(backButton, 'click', () => {
|
on(backButton, 'click', () => {
|
||||||
controls.showMenuPanel.call(this, 'home', false);
|
showMenuPanel.call(this, 'home', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to pane
|
// Add to pane
|
||||||
@ -1516,30 +1556,30 @@ const controls = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
popup.appendChild(inner);
|
popup.appendChild(inner);
|
||||||
control.appendChild(popup);
|
wrapper.appendChild(popup);
|
||||||
container.appendChild(control);
|
container.appendChild(wrapper);
|
||||||
|
|
||||||
this.elements.settings.popup = popup;
|
this.elements.settings.popup = popup;
|
||||||
this.elements.settings.menu = control;
|
this.elements.settings.menu = wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Picture in picture button
|
// Picture in picture button
|
||||||
if (this.config.controls.includes('pip') && support.pip) {
|
if (control === 'pip' && support.pip) {
|
||||||
container.appendChild(controls.createButton.call(this, 'pip'));
|
container.appendChild(createButton.call(this, 'pip', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Airplay button
|
// Airplay button
|
||||||
if (this.config.controls.includes('airplay') && support.airplay) {
|
if (control === 'airplay' && support.airplay) {
|
||||||
container.appendChild(controls.createButton.call(this, 'airplay'));
|
container.appendChild(createButton.call(this, 'airplay', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download button
|
// Download button
|
||||||
if (this.config.controls.includes('download')) {
|
if (control === 'download') {
|
||||||
const attributes = {
|
const attributes = extend({}, defaultAttributes, {
|
||||||
element: 'a',
|
element: 'a',
|
||||||
href: this.download,
|
href: this.download,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
};
|
});
|
||||||
|
|
||||||
const { download } = this.config.urls;
|
const { download } = this.config.urls;
|
||||||
|
|
||||||
@ -1550,27 +1590,21 @@ const controls = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(controls.createButton.call(this, 'download', attributes));
|
container.appendChild(createButton.call(this, 'download', attributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle fullscreen button
|
// Toggle fullscreen button
|
||||||
if (this.config.controls.includes('fullscreen')) {
|
if (control === 'fullscreen') {
|
||||||
container.appendChild(controls.createButton.call(this, 'fullscreen'));
|
container.appendChild(createButton.call(this, 'fullscreen', defaultAttributes));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Larger overlaid play button
|
|
||||||
if (this.config.controls.includes('play-large')) {
|
|
||||||
this.elements.container.appendChild(controls.createButton.call(this, 'play-large'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.elements.controls = container;
|
|
||||||
|
|
||||||
// Set available quality levels
|
// Set available quality levels
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
|
setQualityMenu.call(this, html5.getQualityOptions.call(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
controls.setSpeedMenu.call(this);
|
setSpeedMenu.call(this);
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
// https://webkit.org/blog/7929/designing-websites-for-iphone-x/
|
// https://webkit.org/blog/7929/designing-websites-for-iphone-x/
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import { repaint } from './utils/animation';
|
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
@ -73,9 +72,6 @@ function toggleFallback(toggle = false) {
|
|||||||
.filter(part => part.trim() !== property)
|
.filter(part => part.trim() !== property)
|
||||||
.join(',');
|
.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force a repaint as sometimes Safari doesn't want to fill the screen
|
|
||||||
setTimeout(() => repaint(this.target), 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle button and fire events
|
// Toggle button and fire events
|
||||||
|
@ -6,6 +6,7 @@ import support from './support';
|
|||||||
import { removeElement } from './utils/elements';
|
import { removeElement } from './utils/elements';
|
||||||
import { triggerEvent } from './utils/events';
|
import { triggerEvent } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
|
import { setAspectRatio } from './utils/style';
|
||||||
|
|
||||||
const html5 = {
|
const html5 = {
|
||||||
getSources() {
|
getSources() {
|
||||||
@ -43,12 +44,17 @@ const html5 = {
|
|||||||
|
|
||||||
const player = this;
|
const player = this;
|
||||||
|
|
||||||
|
// Set aspect ratio if fixed
|
||||||
|
if (!is.empty(this.config.ratio)) {
|
||||||
|
setAspectRatio.call(player);
|
||||||
|
}
|
||||||
|
|
||||||
// Quality
|
// Quality
|
||||||
Object.defineProperty(player.media, 'quality', {
|
Object.defineProperty(player.media, 'quality', {
|
||||||
get() {
|
get() {
|
||||||
// Get sources
|
// Get sources
|
||||||
const sources = html5.getSources.call(player);
|
const sources = html5.getSources.call(player);
|
||||||
const source = sources.find(source => source.getAttribute('src') === player.source);
|
const source = sources.find(s => s.getAttribute('src') === player.source);
|
||||||
|
|
||||||
// Return size, if match is found
|
// Return size, if match is found
|
||||||
return source && Number(source.getAttribute('size'));
|
return source && Number(source.getAttribute('size'));
|
||||||
@ -56,9 +62,8 @@ const html5 = {
|
|||||||
set(input) {
|
set(input) {
|
||||||
// Get sources
|
// Get sources
|
||||||
const sources = html5.getSources.call(player);
|
const sources = html5.getSources.call(player);
|
||||||
|
|
||||||
// Get first match for requested size
|
// Get first match for requested size
|
||||||
const source = sources.find(source => Number(source.getAttribute('size')) === input);
|
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||||
|
|
||||||
// No matching source found
|
// No matching source found
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
@ -9,7 +9,7 @@ import browser from './utils/browser';
|
|||||||
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
|
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||||
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
|
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import { setAspectRatio } from './utils/style';
|
import { getAspectRatio, setAspectRatio } from './utils/style';
|
||||||
|
|
||||||
class Listeners {
|
class Listeners {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
@ -275,17 +275,16 @@ class Listeners {
|
|||||||
elements.container,
|
elements.container,
|
||||||
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
|
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
|
||||||
event => {
|
event => {
|
||||||
const { controls } = elements;
|
const { controls: controlsElement } = elements;
|
||||||
|
|
||||||
// Remove button states for fullscreen
|
// Remove button states for fullscreen
|
||||||
if (controls && event.type === 'enterfullscreen') {
|
if (controlsElement && event.type === 'enterfullscreen') {
|
||||||
controls.pressed = false;
|
controlsElement.pressed = false;
|
||||||
controls.hover = false;
|
controlsElement.hover = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show, then hide after a timeout unless another control event occurs
|
// Show, then hide after a timeout unless another control event occurs
|
||||||
const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
|
const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
|
||||||
|
|
||||||
let delay = 0;
|
let delay = 0;
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
@ -302,14 +301,6 @@ class Listeners {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Force edge to repaint on exit fullscreen
|
|
||||||
// TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen
|
|
||||||
/* if (browser.isEdge) {
|
|
||||||
on.call(player, elements.container, 'exitfullscreen', () => {
|
|
||||||
setTimeout(() => repaint(elements.container), 100);
|
|
||||||
});
|
|
||||||
} */
|
|
||||||
|
|
||||||
// Set a gutter for Vimeo
|
// Set a gutter for Vimeo
|
||||||
const setGutter = (ratio, padding, toggle) => {
|
const setGutter = (ratio, padding, toggle) => {
|
||||||
if (!player.isVimeo) {
|
if (!player.isVimeo) {
|
||||||
@ -317,10 +308,10 @@ class Listeners {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const target = player.elements.wrapper.firstChild;
|
const target = player.elements.wrapper.firstChild;
|
||||||
const [, height] = ratio.split(':').map(Number);
|
const [, y] = ratio;
|
||||||
const [videoWidth, videoHeight] = player.embed.ratio.split(':').map(Number);
|
const [videoX, videoY] = getAspectRatio.call(player);
|
||||||
|
|
||||||
target.style.maxWidth = toggle ? `${(height / videoHeight) * videoWidth}px` : null;
|
target.style.maxWidth = toggle ? `${(y / videoY) * videoX}px` : null;
|
||||||
target.style.margin = toggle ? '0 auto' : null;
|
target.style.margin = toggle ? '0 auto' : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -338,20 +329,24 @@ class Listeners {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resized = () => {
|
const resized = () => {
|
||||||
window.clearTimeout(timers.resized);
|
clearTimeout(timers.resized);
|
||||||
timers.resized = window.setTimeout(setPlayerSize, 50);
|
timers.resized = setTimeout(setPlayerSize, 50);
|
||||||
};
|
};
|
||||||
|
|
||||||
on.call(player, elements.container, 'enterfullscreen exitfullscreen', event => {
|
on.call(player, elements.container, 'enterfullscreen exitfullscreen', event => {
|
||||||
const { target, usingNative } = player.fullscreen;
|
const { target, usingNative } = player.fullscreen;
|
||||||
|
|
||||||
// Ignore for iOS native
|
// Ignore events not from target
|
||||||
if (!player.isEmbed || target !== elements.container) {
|
if (target !== elements.container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not an embed and no ratio specified
|
||||||
|
if (!player.isEmbed && is.empty(player.config.ratio)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEnter = event.type === 'enterfullscreen';
|
const isEnter = event.type === 'enterfullscreen';
|
||||||
|
|
||||||
// Set the player size when entering fullscreen to viewport size
|
// Set the player size when entering fullscreen to viewport size
|
||||||
const { padding, ratio } = setPlayerSize(isEnter);
|
const { padding, ratio } = setPlayerSize(isEnter);
|
||||||
|
|
||||||
@ -486,7 +481,7 @@ class Listeners {
|
|||||||
|
|
||||||
// Update download link when ready and if quality changes
|
// Update download link when ready and if quality changes
|
||||||
on.call(player, player.media, 'ready qualitychange', () => {
|
on.call(player, player.media, 'ready qualitychange', () => {
|
||||||
controls.setDownloadLink.call(player);
|
controls.setDownloadUrl.call(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Proxy events to container
|
// Proxy events to container
|
||||||
@ -518,7 +513,7 @@ class Listeners {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only call default handler if not prevented in custom handler
|
// Only call default handler if not prevented in custom handler
|
||||||
if (returned && is.function(defaultHandler)) {
|
if (returned !== false && is.function(defaultHandler)) {
|
||||||
defaultHandler.call(player, event);
|
defaultHandler.call(player, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,7 +537,6 @@ class Listeners {
|
|||||||
controls() {
|
controls() {
|
||||||
const { player } = this;
|
const { player } = this;
|
||||||
const { elements } = player;
|
const { elements } = player;
|
||||||
|
|
||||||
// IE doesn't support input event, so we fallback to change
|
// IE doesn't support input event, so we fallback to change
|
||||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||||
|
|
||||||
@ -678,7 +672,6 @@ class Listeners {
|
|||||||
|
|
||||||
// Was playing before?
|
// Was playing before?
|
||||||
const play = seek.hasAttribute(attribute);
|
const play = seek.hasAttribute(attribute);
|
||||||
|
|
||||||
// Done seeking
|
// Done seeking
|
||||||
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
|
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
|
||||||
|
|
||||||
@ -706,7 +699,6 @@ class Listeners {
|
|||||||
inputEvent,
|
inputEvent,
|
||||||
event => {
|
event => {
|
||||||
const seek = event.currentTarget;
|
const seek = event.currentTarget;
|
||||||
|
|
||||||
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
|
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
|
||||||
let seekTo = seek.getAttribute('seek-value');
|
let seekTo = seek.getAttribute('seek-value');
|
||||||
|
|
||||||
@ -806,7 +798,7 @@ class Listeners {
|
|||||||
|
|
||||||
// Show controls when they receive focus (e.g., when using keyboard tab key)
|
// Show controls when they receive focus (e.g., when using keyboard tab key)
|
||||||
this.bind(elements.controls, 'focusin', () => {
|
this.bind(elements.controls, 'focusin', () => {
|
||||||
const { config, elements, timers } = player;
|
const { config, timers } = player;
|
||||||
|
|
||||||
// Skip transition to prevent focus from scrolling the parent element
|
// Skip transition to prevent focus from scrolling the parent element
|
||||||
toggleClass(elements.controls, config.classNames.noTransition, true);
|
toggleClass(elements.controls, config.classNames.noTransition, true);
|
||||||
@ -837,10 +829,8 @@ class Listeners {
|
|||||||
// Detect "natural" scroll - suppored on OS X Safari only
|
// Detect "natural" scroll - suppored on OS X Safari only
|
||||||
// Other browsers on OS X will be inverted until support improves
|
// Other browsers on OS X will be inverted until support improves
|
||||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||||
|
|
||||||
// Get delta from event. Invert if `inverted` is true
|
// Get delta from event. Invert if `inverted` is true
|
||||||
const [x, y] = [event.deltaX, -event.deltaY].map(value => (inverted ? -value : value));
|
const [x, y] = [event.deltaX, -event.deltaY].map(value => (inverted ? -value : value));
|
||||||
|
|
||||||
// Using the biggest delta, normalize to 1 or -1 (or 0 if no delta)
|
// Using the biggest delta, normalize to 1 or -1 (or 0 if no delta)
|
||||||
const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);
|
const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);
|
||||||
|
|
||||||
|
@ -10,14 +10,28 @@ import { createElement } from '../utils/elements';
|
|||||||
import { triggerEvent } from '../utils/events';
|
import { triggerEvent } from '../utils/events';
|
||||||
import i18n from '../utils/i18n';
|
import i18n from '../utils/i18n';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import loadScript from '../utils/loadScript';
|
import loadScript from '../utils/load-script';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
import { buildUrlParams } from '../utils/urls';
|
import { buildUrlParams } from '../utils/urls';
|
||||||
|
|
||||||
|
const destroy = instance => {
|
||||||
|
// Destroy our adsManager
|
||||||
|
if (instance.manager) {
|
||||||
|
instance.manager.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy our adsManager
|
||||||
|
if (instance.elements.displayContainer) {
|
||||||
|
instance.elements.displayContainer.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.elements.container.remove();
|
||||||
|
};
|
||||||
|
|
||||||
class Ads {
|
class Ads {
|
||||||
/**
|
/**
|
||||||
* Ads constructor.
|
* Ads constructor.
|
||||||
* @param {object} player
|
* @param {Object} player
|
||||||
* @return {Ads}
|
* @return {Ads}
|
||||||
*/
|
*/
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
@ -63,7 +77,10 @@ class Ads {
|
|||||||
* Load the IMA SDK
|
* Load the IMA SDK
|
||||||
*/
|
*/
|
||||||
load() {
|
load() {
|
||||||
if (this.enabled) {
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
||||||
if (!is.object(window.google) || !is.object(window.google.ima)) {
|
if (!is.object(window.google) || !is.object(window.google.ima)) {
|
||||||
loadScript(this.player.config.urls.googleIMA.sdk)
|
loadScript(this.player.config.urls.googleIMA.sdk)
|
||||||
@ -78,12 +95,16 @@ class Ads {
|
|||||||
this.ready();
|
this.ready();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ads instance ready
|
* Get the ads instance ready
|
||||||
*/
|
*/
|
||||||
ready() {
|
ready() {
|
||||||
|
// Double check we're enabled
|
||||||
|
if (!this.enabled) {
|
||||||
|
destroy(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Start ticking our safety timer. If the whole advertisement
|
// Start ticking our safety timer. If the whole advertisement
|
||||||
// thing doesn't resolve within our set time; we bail
|
// thing doesn't resolve within our set time; we bail
|
||||||
this.startSafetyTimer(12000, 'ready()');
|
this.startSafetyTimer(12000, 'ready()');
|
||||||
@ -198,7 +219,7 @@ class Ads {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the ad countdown
|
* Update the ad countdown
|
||||||
* @param {boolean} start
|
* @param {Boolean} start
|
||||||
*/
|
*/
|
||||||
pollCountdown(start = false) {
|
pollCountdown(start = false) {
|
||||||
if (!start) {
|
if (!start) {
|
||||||
@ -240,16 +261,13 @@ class Ads {
|
|||||||
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||||
this.cuePoints = this.manager.getCuePoints();
|
this.cuePoints = this.manager.getCuePoints();
|
||||||
|
|
||||||
// Set volume to match player
|
|
||||||
this.manager.setVolume(this.player.volume);
|
|
||||||
|
|
||||||
// Add listeners to the required events
|
// Add listeners to the required events
|
||||||
// Advertisement error events
|
// Advertisement error events
|
||||||
this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
||||||
|
|
||||||
// Advertisement regular events
|
// Advertisement regular events
|
||||||
Object.keys(google.ima.AdEvent.Type).forEach(type => {
|
Object.keys(google.ima.AdEvent.Type).forEach(type => {
|
||||||
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
|
this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolve our adsManager
|
// Resolve our adsManager
|
||||||
@ -285,7 +303,6 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
onAdEvent(event) {
|
onAdEvent(event) {
|
||||||
const { container } = this.player.elements;
|
const { container } = this.player.elements;
|
||||||
|
|
||||||
// Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
|
// Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
|
||||||
// don't have ad object associated
|
// don't have ad object associated
|
||||||
const ad = event.getAd();
|
const ad = event.getAd();
|
||||||
@ -293,19 +310,18 @@ class Ads {
|
|||||||
|
|
||||||
// Proxy event
|
// Proxy event
|
||||||
const dispatchEvent = type => {
|
const dispatchEvent = type => {
|
||||||
const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
|
triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
|
||||||
triggerEvent.call(this.player, this.player.media, event);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bubble the event
|
||||||
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case google.ima.AdEvent.Type.LOADED:
|
case google.ima.AdEvent.Type.LOADED:
|
||||||
// This is the first event sent for an ad - it is possible to determine whether the
|
// This is the first event sent for an ad - it is possible to determine whether the
|
||||||
// ad is a video ad or an overlay
|
// ad is a video ad or an overlay
|
||||||
this.trigger('loaded');
|
this.trigger('loaded');
|
||||||
|
|
||||||
// Bubble event
|
|
||||||
dispatchEvent(event.type);
|
|
||||||
|
|
||||||
// Start countdown
|
// Start countdown
|
||||||
this.pollCountdown(true);
|
this.pollCountdown(true);
|
||||||
|
|
||||||
@ -317,15 +333,19 @@ class Ads {
|
|||||||
|
|
||||||
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
|
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
|
||||||
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
|
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case google.ima.AdEvent.Type.STARTED:
|
||||||
|
// Set volume to match player
|
||||||
|
this.manager.setVolume(this.player.volume);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
||||||
// All ads for the current videos are done. We can now request new advertisements
|
// All ads for the current videos are done. We can now request new advertisements
|
||||||
// in case the video is re-played
|
// in case the video is re-played
|
||||||
|
|
||||||
// Fire event
|
|
||||||
dispatchEvent(event.type);
|
|
||||||
|
|
||||||
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
// 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.
|
// So here we load a new video when all ads are done.
|
||||||
// Then we load new ads within a new adsManager. When the video
|
// Then we load new ads within a new adsManager. When the video
|
||||||
@ -350,6 +370,7 @@ class Ads {
|
|||||||
// playing when the IMA SDK is ready or has failed
|
// playing when the IMA SDK is ready or has failed
|
||||||
|
|
||||||
this.loadAds();
|
this.loadAds();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
@ -357,8 +378,6 @@ class Ads {
|
|||||||
// for example display a pause button and remaining time. Fired when content should
|
// 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
|
// be paused. This usually happens right before an ad is about to cover the content
|
||||||
|
|
||||||
dispatchEvent(event.type);
|
|
||||||
|
|
||||||
this.pauseContent();
|
this.pauseContent();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -369,26 +388,17 @@ class Ads {
|
|||||||
// Fired when content should be resumed. This usually happens when an ad finishes
|
// Fired when content should be resumed. This usually happens when an ad finishes
|
||||||
// or collapses
|
// or collapses
|
||||||
|
|
||||||
dispatchEvent(event.type);
|
|
||||||
|
|
||||||
this.pollCountdown();
|
this.pollCountdown();
|
||||||
|
|
||||||
this.resumeContent();
|
this.resumeContent();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.STARTED:
|
|
||||||
case google.ima.AdEvent.Type.MIDPOINT:
|
|
||||||
case google.ima.AdEvent.Type.COMPLETE:
|
|
||||||
case google.ima.AdEvent.Type.IMPRESSION:
|
|
||||||
case google.ima.AdEvent.Type.CLICK:
|
|
||||||
dispatchEvent(event.type);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.LOG:
|
case google.ima.AdEvent.Type.LOG:
|
||||||
if (adData.adError) {
|
if (adData.adError) {
|
||||||
this.player.debug.warn(`Non-fatal ad error: ${adData.adError.getMessage()}`);
|
this.player.debug.warn(`Non-fatal ad error: ${adData.adError.getMessage()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -463,6 +473,9 @@ class Ads {
|
|||||||
// Play the requested advertisement whenever the adsManager is ready
|
// Play the requested advertisement whenever the adsManager is ready
|
||||||
this.managerPromise
|
this.managerPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
// Set volume to match player
|
||||||
|
this.manager.setVolume(this.player.volume);
|
||||||
|
|
||||||
// Initialize the container. Must be done via a user action on mobile devices
|
// Initialize the container. Must be done via a user action on mobile devices
|
||||||
this.elements.displayContainer.initialize();
|
this.elements.displayContainer.initialize();
|
||||||
|
|
||||||
@ -559,7 +572,7 @@ class Ads {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles callbacks after an ad event was invoked
|
* Handles callbacks after an ad event was invoked
|
||||||
* @param {string} event - Event type
|
* @param {String} event - Event type
|
||||||
*/
|
*/
|
||||||
trigger(event, ...args) {
|
trigger(event, ...args) {
|
||||||
const handlers = this.events[event];
|
const handlers = this.events[event];
|
||||||
@ -575,8 +588,8 @@ class Ads {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add event listeners
|
* Add event listeners
|
||||||
* @param {string} event - Event type
|
* @param {String} event - Event type
|
||||||
* @param {function} callback - Callback for when event occurs
|
* @param {Function} callback - Callback for when event occurs
|
||||||
* @return {Ads}
|
* @return {Ads}
|
||||||
*/
|
*/
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
@ -594,8 +607,8 @@ class Ads {
|
|||||||
* The advertisement has 12 seconds to get its things together. We stop this timer when the
|
* The advertisement has 12 seconds to get its things together. We stop this timer when the
|
||||||
* advertisement is playing, or when a user action is required to start, then we clear the
|
* advertisement is playing, or when a user action is required to start, then we clear the
|
||||||
* timer on ad ready
|
* timer on ad ready
|
||||||
* @param {number} time
|
* @param {Number} time
|
||||||
* @param {string} from
|
* @param {String} from
|
||||||
*/
|
*/
|
||||||
startSafetyTimer(time, from) {
|
startSafetyTimer(time, from) {
|
||||||
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||||
@ -608,7 +621,7 @@ class Ads {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear our safety timer(s)
|
* Clear our safety timer(s)
|
||||||
* @param {string} from
|
* @param {String} from
|
||||||
*/
|
*/
|
||||||
clearSafetyTimer(from) {
|
clearSafetyTimer(from) {
|
||||||
if (!is.nullOrUndefined(this.safetyTimer)) {
|
if (!is.nullOrUndefined(this.safetyTimer)) {
|
||||||
|
@ -17,17 +17,17 @@ const parseVtt = vttDataString => {
|
|||||||
if (!is.number(result.startTime)) {
|
if (!is.number(result.startTime)) {
|
||||||
// The line with start and end times on it is the first line of interest
|
// The line with start and end times on it is the first line of interest
|
||||||
const matchTimes = line.match(
|
const matchTimes = line.match(
|
||||||
/([0-9]{2}):([0-9]{2}):([0-9]{2}).([0-9]{2,3})( ?--> ?)([0-9]{2}):([0-9]{2}):([0-9]{2}).([0-9]{2,3})/,
|
/([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})( ?--> ?)([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})/,
|
||||||
); // Note that this currently ignores caption formatting directives that are optionally on the end of this line - fine for non-captions VTT
|
); // Note that this currently ignores caption formatting directives that are optionally on the end of this line - fine for non-captions VTT
|
||||||
|
|
||||||
if (matchTimes) {
|
if (matchTimes) {
|
||||||
result.startTime =
|
result.startTime =
|
||||||
Number(matchTimes[1]) * 60 * 60 +
|
Number(matchTimes[1] || 0) * 60 * 60 +
|
||||||
Number(matchTimes[2]) * 60 +
|
Number(matchTimes[2]) * 60 +
|
||||||
Number(matchTimes[3]) +
|
Number(matchTimes[3]) +
|
||||||
Number(`0.${matchTimes[4]}`);
|
Number(`0.${matchTimes[4]}`);
|
||||||
result.endTime =
|
result.endTime =
|
||||||
Number(matchTimes[6]) * 60 * 60 +
|
Number(matchTimes[6] || 0) * 60 * 60 +
|
||||||
Number(matchTimes[7]) * 60 +
|
Number(matchTimes[7]) * 60 +
|
||||||
Number(matchTimes[8]) +
|
Number(matchTimes[8]) +
|
||||||
Number(`0.${matchTimes[9]}`);
|
Number(`0.${matchTimes[9]}`);
|
||||||
@ -100,6 +100,10 @@ class PreviewThumbnails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.getThumbnails().then(() => {
|
this.getThumbnails().then(() => {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Render DOM elements
|
// Render DOM elements
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
@ -121,7 +125,6 @@ class PreviewThumbnails {
|
|||||||
|
|
||||||
// If string, convert into single-element list
|
// If string, convert into single-element list
|
||||||
const urls = is.string(src) ? [src] : src;
|
const urls = is.string(src) ? [src] : src;
|
||||||
|
|
||||||
// Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
// Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||||
const promises = urls.map(u => this.getThumbnail(u));
|
const promises = urls.map(u => this.getThumbnail(u));
|
||||||
|
|
||||||
@ -148,7 +151,12 @@ class PreviewThumbnails {
|
|||||||
|
|
||||||
// If the URLs don't start with '/', then we need to set their relative path to be the location of the VTT file
|
// If the URLs don't start with '/', then we need to set their relative path to be the location of the VTT file
|
||||||
// If the URLs do start with '/', then they obviously don't need a prefix, so it will remain blank
|
// If the URLs do start with '/', then they obviously don't need a prefix, so it will remain blank
|
||||||
if (!thumbnail.frames[0].text.startsWith('/')) {
|
// If the thumbnail URLs start with with none of '/', 'http://' or 'https://', then we need to set their relative path to be the location of the VTT file
|
||||||
|
if (
|
||||||
|
!thumbnail.frames[0].text.startsWith('/') &&
|
||||||
|
!thumbnail.frames[0].text.startsWith('http://') &&
|
||||||
|
!thumbnail.frames[0].text.startsWith('https://')
|
||||||
|
) {
|
||||||
thumbnail.urlPrefix = url.substring(0, url.lastIndexOf('/') + 1);
|
thumbnail.urlPrefix = url.substring(0, url.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +228,7 @@ class PreviewThumbnails {
|
|||||||
// Only act on left mouse button (0), or touch device (event.button is false)
|
// Only act on left mouse button (0), or touch device (event.button is false)
|
||||||
if (event.button === false || event.button === 0) {
|
if (event.button === false || event.button === 0) {
|
||||||
this.mouseDown = true;
|
this.mouseDown = true;
|
||||||
|
|
||||||
// Wait until media has a duration
|
// Wait until media has a duration
|
||||||
if (this.player.media.duration) {
|
if (this.player.media.duration) {
|
||||||
this.toggleScrubbingContainer(true);
|
this.toggleScrubbingContainer(true);
|
||||||
@ -293,7 +302,9 @@ class PreviewThumbnails {
|
|||||||
this.elements.thumb.container.appendChild(timeContainer);
|
this.elements.thumb.container.appendChild(timeContainer);
|
||||||
|
|
||||||
// Inject the whole thumb
|
// Inject the whole thumb
|
||||||
|
if (is.element(this.player.elements.progress)) {
|
||||||
this.player.elements.progress.appendChild(this.elements.thumb.container);
|
this.player.elements.progress.appendChild(this.elements.thumb.container);
|
||||||
|
}
|
||||||
|
|
||||||
// Create HTML element: plyr__preview-scrubbing-container
|
// Create HTML element: plyr__preview-scrubbing-container
|
||||||
this.elements.scrubbing.container = createElement('div', {
|
this.elements.scrubbing.container = createElement('div', {
|
||||||
@ -307,7 +318,6 @@ class PreviewThumbnails {
|
|||||||
if (this.mouseDown) {
|
if (this.mouseDown) {
|
||||||
this.setScrubbingContainerSize();
|
this.setScrubbingContainerSize();
|
||||||
} else {
|
} else {
|
||||||
this.toggleThumbContainer(true);
|
|
||||||
this.setThumbContainerSizeAndPos();
|
this.setThumbContainerSizeAndPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +329,10 @@ class PreviewThumbnails {
|
|||||||
const hasThumb = thumbNum >= 0;
|
const hasThumb = thumbNum >= 0;
|
||||||
let qualityIndex = 0;
|
let qualityIndex = 0;
|
||||||
|
|
||||||
|
// Show the thumb container if we're not scrubbing
|
||||||
|
if (!this.mouseDown) {
|
||||||
this.toggleThumbContainer(hasThumb);
|
this.toggleThumbContainer(hasThumb);
|
||||||
|
}
|
||||||
|
|
||||||
// No matching thumb found
|
// No matching thumb found
|
||||||
if (!hasThumb) {
|
if (!hasThumb) {
|
||||||
@ -416,7 +429,9 @@ class PreviewThumbnails {
|
|||||||
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
|
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
|
||||||
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
|
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
|
||||||
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
|
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
image.dataset.deleting = true;
|
image.dataset.deleting = true;
|
||||||
|
|
||||||
// This has to be set before the timeout - to prevent issues switching between hover and scrub
|
// This has to be set before the timeout - to prevent issues switching between hover and scrub
|
||||||
const { currentImageContainer } = this;
|
const { currentImageContainer } = this;
|
||||||
|
|
||||||
@ -457,7 +472,6 @@ class PreviewThumbnails {
|
|||||||
|
|
||||||
const { urlPrefix } = this.thumbnails[0];
|
const { urlPrefix } = this.thumbnails[0];
|
||||||
const thumbURL = urlPrefix + newThumbFilename;
|
const thumbURL = urlPrefix + newThumbFilename;
|
||||||
|
|
||||||
const previewImage = new Image();
|
const previewImage = new Image();
|
||||||
previewImage.src = thumbURL;
|
previewImage.src = thumbURL;
|
||||||
previewImage.onload = () => {
|
previewImage.onload = () => {
|
||||||
@ -591,11 +605,9 @@ class PreviewThumbnails {
|
|||||||
const seekbarRect = this.player.elements.progress.getBoundingClientRect();
|
const seekbarRect = this.player.elements.progress.getBoundingClientRect();
|
||||||
const plyrRect = this.player.elements.container.getBoundingClientRect();
|
const plyrRect = this.player.elements.container.getBoundingClientRect();
|
||||||
const { container } = this.elements.thumb;
|
const { container } = this.elements.thumb;
|
||||||
|
|
||||||
// Find the lowest and highest desired left-position, so we don't slide out the side of the video container
|
// Find the lowest and highest desired left-position, so we don't slide out the side of the video container
|
||||||
const minVal = plyrRect.left - seekbarRect.left + 10;
|
const minVal = plyrRect.left - seekbarRect.left + 10;
|
||||||
const maxVal = plyrRect.right - seekbarRect.left - container.clientWidth - 10;
|
const maxVal = plyrRect.right - seekbarRect.left - container.clientWidth - 10;
|
||||||
|
|
||||||
// Set preview container position to: mousepos, minus seekbar.left, minus half of previewContainer.clientWidth
|
// Set preview container position to: mousepos, minus seekbar.left, minus half of previewContainer.clientWidth
|
||||||
let previewPos = this.mousePosX - seekbarRect.left - container.clientWidth / 2;
|
let previewPos = this.mousePosX - seekbarRect.left - container.clientWidth / 2;
|
||||||
|
|
||||||
@ -626,9 +638,13 @@ class PreviewThumbnails {
|
|||||||
// Find difference between height and preview container height
|
// Find difference between height and preview container height
|
||||||
const multiplier = this.thumbContainerHeight / frame.h;
|
const multiplier = this.thumbContainerHeight / frame.h;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
previewImage.style.height = `${Math.floor(previewImage.naturalHeight * multiplier)}px`;
|
previewImage.style.height = `${Math.floor(previewImage.naturalHeight * multiplier)}px`;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
|
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
previewImage.style.left = `-${frame.x * multiplier}px`;
|
previewImage.style.left = `-${frame.x * multiplier}px`;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
previewImage.style.top = `-${frame.y * multiplier}px`;
|
previewImage.style.top = `-${frame.y * multiplier}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
|||||||
import { triggerEvent } from '../utils/events';
|
import { triggerEvent } from '../utils/events';
|
||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import loadScript from '../utils/loadScript';
|
import loadScript from '../utils/load-script';
|
||||||
import { extend } from '../utils/objects';
|
import { extend } from '../utils/objects';
|
||||||
import { format, stripHTML } from '../utils/strings';
|
import { format, stripHTML } from '../utils/strings';
|
||||||
import { setAspectRatio } from '../utils/style';
|
import { setAspectRatio } from '../utils/style';
|
||||||
@ -48,14 +48,14 @@ const vimeo = {
|
|||||||
// Set intial ratio
|
// Set intial ratio
|
||||||
setAspectRatio.call(this);
|
setAspectRatio.call(this);
|
||||||
|
|
||||||
// Load the API if not already
|
// Load the SDK if not already
|
||||||
if (!is.object(window.Vimeo)) {
|
if (!is.object(window.Vimeo)) {
|
||||||
loadScript(this.config.urls.vimeo.sdk)
|
loadScript(this.config.urls.vimeo.sdk)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
vimeo.ready.call(this);
|
vimeo.ready.call(this);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.debug.warn('Vimeo API failed to load', error);
|
this.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
vimeo.ready.call(this);
|
vimeo.ready.call(this);
|
||||||
@ -91,7 +91,6 @@ const vimeo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = parseId(source);
|
const id = parseId(source);
|
||||||
|
|
||||||
// Build an iframe
|
// Build an iframe
|
||||||
const iframe = createElement('iframe');
|
const iframe = createElement('iframe');
|
||||||
const src = format(player.config.urls.vimeo.iframe, id, params);
|
const src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
@ -102,7 +101,6 @@ const vimeo = {
|
|||||||
|
|
||||||
// Get poster, if already set
|
// Get poster, if already set
|
||||||
const { poster } = player;
|
const { poster } = player;
|
||||||
|
|
||||||
// Inject the package
|
// Inject the package
|
||||||
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
|
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
|
||||||
wrapper.appendChild(iframe);
|
wrapper.appendChild(iframe);
|
||||||
@ -259,7 +257,7 @@ const vimeo = {
|
|||||||
.getVideoUrl()
|
.getVideoUrl()
|
||||||
.then(value => {
|
.then(value => {
|
||||||
currentSrc = value;
|
currentSrc = value;
|
||||||
controls.setDownloadLink.call(player);
|
controls.setDownloadUrl.call(player);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.debug.warn(error);
|
this.debug.warn(error);
|
||||||
@ -281,8 +279,8 @@ const vimeo = {
|
|||||||
// Set aspect ratio based on video size
|
// Set aspect ratio based on video size
|
||||||
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
||||||
const [width, height] = dimensions;
|
const [width, height] = dimensions;
|
||||||
player.embed.ratio = `${width}:${height}`;
|
player.embed.ratio = [width, height];
|
||||||
setAspectRatio.call(this, player.embed.ratio);
|
setAspectRatio.call(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set autopause
|
// Set autopause
|
||||||
|
@ -7,8 +7,8 @@ import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
|||||||
import { triggerEvent } from '../utils/events';
|
import { triggerEvent } from '../utils/events';
|
||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import loadImage from '../utils/loadImage';
|
import loadImage from '../utils/load-image';
|
||||||
import loadScript from '../utils/loadScript';
|
import loadScript from '../utils/load-script';
|
||||||
import { extend } from '../utils/objects';
|
import { extend } from '../utils/objects';
|
||||||
import { format, generateId } from '../utils/strings';
|
import { format, generateId } from '../utils/strings';
|
||||||
import { setAspectRatio } from '../utils/style';
|
import { setAspectRatio } from '../utils/style';
|
||||||
@ -34,78 +34,78 @@ function assurePlaybackState(play) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHost(config) {
|
||||||
|
if (config.noCookie) {
|
||||||
|
return 'https://www.youtube-nocookie.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.protocol === 'http:') {
|
||||||
|
return 'http://www.youtube.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use YouTube's default
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const youtube = {
|
const youtube = {
|
||||||
setup() {
|
setup() {
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||||
|
|
||||||
// Set aspect ratio
|
|
||||||
setAspectRatio.call(this);
|
|
||||||
|
|
||||||
// Setup API
|
// Setup API
|
||||||
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
||||||
youtube.ready.call(this);
|
youtube.ready.call(this);
|
||||||
} else {
|
} else {
|
||||||
// Load the API
|
// Reference current global callback
|
||||||
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
const callback = window.onYouTubeIframeAPIReady;
|
||||||
this.debug.warn('YouTube API failed to load', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup callback for the API
|
|
||||||
// YouTube has it's own system of course...
|
|
||||||
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
|
|
||||||
|
|
||||||
// Add to queue
|
|
||||||
window.onYouTubeReadyCallbacks.push(() => {
|
|
||||||
youtube.ready.call(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set callback to process queue
|
// Set callback to process queue
|
||||||
window.onYouTubeIframeAPIReady = () => {
|
window.onYouTubeIframeAPIReady = () => {
|
||||||
window.onYouTubeReadyCallbacks.forEach(callback => {
|
// Call global callback if set
|
||||||
|
if (is.function(callback)) {
|
||||||
callback();
|
callback();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
youtube.ready.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Load the SDK
|
||||||
|
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
||||||
|
this.debug.warn('YouTube API failed to load', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get the media title
|
// Get the media title
|
||||||
getTitle(videoId) {
|
getTitle(videoId) {
|
||||||
// Try via undocumented API method first
|
const url = format(this.config.urls.youtube.api, videoId);
|
||||||
// This method disappears now and then though...
|
|
||||||
// https://github.com/sampotts/plyr/issues/709
|
|
||||||
if (is.function(this.embed.getVideoData)) {
|
|
||||||
const { title } = this.embed.getVideoData();
|
|
||||||
|
|
||||||
if (is.empty(title)) {
|
|
||||||
this.config.title = title;
|
|
||||||
ui.setTitle.call(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or via Google API
|
|
||||||
const key = this.config.keys.google;
|
|
||||||
if (is.string(key) && !is.empty(key)) {
|
|
||||||
const url = format(this.config.urls.youtube.api, videoId, key);
|
|
||||||
|
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(result => {
|
.then(data => {
|
||||||
if (is.object(result)) {
|
if (is.object(data)) {
|
||||||
this.config.title = result.items[0].snippet.title;
|
const { title, height, width } = data;
|
||||||
|
|
||||||
|
// Set title
|
||||||
|
this.config.title = title;
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
|
|
||||||
|
// Set aspect ratio
|
||||||
|
this.embed.ratio = [width, height];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAspectRatio.call(this);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {
|
||||||
}
|
// Set aspect ratio
|
||||||
|
setAspectRatio.call(this);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// API ready
|
// API ready
|
||||||
ready() {
|
ready() {
|
||||||
const player = this;
|
const player = this;
|
||||||
|
|
||||||
// Ignore already setup (race condition)
|
// Ignore already setup (race condition)
|
||||||
const currentId = player.media.getAttribute('id');
|
const currentId = player.media && player.media.getAttribute('id');
|
||||||
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -121,25 +121,23 @@ const youtube = {
|
|||||||
// Replace the <iframe> with a <div> due to YouTube API issues
|
// Replace the <iframe> with a <div> due to YouTube API issues
|
||||||
const videoId = parseId(source);
|
const videoId = parseId(source);
|
||||||
const id = generateId(player.provider);
|
const id = generateId(player.provider);
|
||||||
|
|
||||||
// Get poster, if already set
|
// Get poster, if already set
|
||||||
const { poster } = player;
|
const { poster } = player;
|
||||||
|
|
||||||
// Replace media element
|
// Replace media element
|
||||||
const container = createElement('div', { id, poster });
|
const container = createElement('div', { id, poster });
|
||||||
player.media = replaceElement(container, player.media);
|
player.media = replaceElement(container, player.media);
|
||||||
|
|
||||||
// Id to poster wrapper
|
// Id to poster wrapper
|
||||||
const posterSrc = format => `https://img.youtube.com/vi/${videoId}/${format}default.jpg`;
|
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
||||||
|
|
||||||
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
||||||
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
||||||
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
||||||
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
||||||
.then(image => ui.setPoster.call(player, image.src))
|
.then(image => ui.setPoster.call(player, image.src))
|
||||||
.then(posterSrc => {
|
.then(src => {
|
||||||
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
||||||
if (!posterSrc.includes('maxres')) {
|
if (!src.includes('maxres')) {
|
||||||
player.elements.poster.style.backgroundSize = 'cover';
|
player.elements.poster.style.backgroundSize = 'cover';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -151,7 +149,7 @@ const youtube = {
|
|||||||
// https://developers.google.com/youtube/iframe_api_reference
|
// https://developers.google.com/youtube/iframe_api_reference
|
||||||
player.embed = new window.YT.Player(id, {
|
player.embed = new window.YT.Player(id, {
|
||||||
videoId,
|
videoId,
|
||||||
host: config.noCookie ? 'https://www.youtube-nocookie.com' : undefined,
|
host: getHost(config),
|
||||||
playerVars: extend(
|
playerVars: extend(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@ -386,7 +384,7 @@ const youtube = {
|
|||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
||||||
if (player.media.paused && !player.embed.hasPlayed) {
|
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
||||||
player.media.pause();
|
player.media.pause();
|
||||||
} else {
|
} else {
|
||||||
assurePlaybackState.call(player, true);
|
assurePlaybackState.call(player, true);
|
||||||
|
200
src/js/plyr.js
200
src/js/plyr.js
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.5.0-beta.5
|
// plyr.js v3.5.6
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -15,7 +15,7 @@ import Fullscreen from './fullscreen';
|
|||||||
import Listeners from './listeners';
|
import Listeners from './listeners';
|
||||||
import media from './media';
|
import media from './media';
|
||||||
import Ads from './plugins/ads';
|
import Ads from './plugins/ads';
|
||||||
import PreviewThumbnails from './plugins/previewThumbnails';
|
import PreviewThumbnails from './plugins/preview-thumbnails';
|
||||||
import source from './source';
|
import source from './source';
|
||||||
import Storage from './storage';
|
import Storage from './storage';
|
||||||
import support from './support';
|
import support from './support';
|
||||||
@ -24,8 +24,10 @@ import { closest } from './utils/arrays';
|
|||||||
import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements';
|
import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements';
|
||||||
import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
|
import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import loadSprite from './utils/loadSprite';
|
import loadSprite from './utils/load-sprite';
|
||||||
|
import { clamp } from './utils/numbers';
|
||||||
import { cloneDeep, extend } from './utils/objects';
|
import { cloneDeep, extend } from './utils/objects';
|
||||||
|
import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style';
|
||||||
import { parseUrl } from './utils/urls';
|
import { parseUrl } from './utils/urls';
|
||||||
|
|
||||||
// Private properties
|
// Private properties
|
||||||
@ -149,7 +151,6 @@ class Plyr {
|
|||||||
// Set media type based on tag or data attribute
|
// Set media type based on tag or data attribute
|
||||||
// Supported: video, audio, vimeo, youtube
|
// Supported: video, audio, vimeo, youtube
|
||||||
const type = this.media.tagName.toLowerCase();
|
const type = this.media.tagName.toLowerCase();
|
||||||
|
|
||||||
// Embed properties
|
// Embed properties
|
||||||
let iframe = null;
|
let iframe = null;
|
||||||
let url = null;
|
let url = null;
|
||||||
@ -301,8 +302,8 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Autoplay if required
|
// Autoplay if required
|
||||||
if (this.config.autoplay) {
|
if (this.isHTML5 && this.config.autoplay) {
|
||||||
this.play();
|
setTimeout(() => this.play(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
@ -322,27 +323,27 @@ class Plyr {
|
|||||||
* Types and provider helpers
|
* Types and provider helpers
|
||||||
*/
|
*/
|
||||||
get isHTML5() {
|
get isHTML5() {
|
||||||
return Boolean(this.provider === providers.html5);
|
return this.provider === providers.html5;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEmbed() {
|
get isEmbed() {
|
||||||
return Boolean(this.isYouTube || this.isVimeo);
|
return this.isYouTube || this.isVimeo;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isYouTube() {
|
get isYouTube() {
|
||||||
return Boolean(this.provider === providers.youtube);
|
return this.provider === providers.youtube;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isVimeo() {
|
get isVimeo() {
|
||||||
return Boolean(this.provider === providers.vimeo);
|
return this.provider === providers.vimeo;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isVideo() {
|
get isVideo() {
|
||||||
return Boolean(this.type === types.video);
|
return this.type === types.video;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAudio() {
|
get isAudio() {
|
||||||
return Boolean(this.type === types.audio);
|
return this.type === types.audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -403,7 +404,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle playback based on current status
|
* Toggle playback based on current status
|
||||||
* @param {boolean} input
|
* @param {Boolean} input
|
||||||
*/
|
*/
|
||||||
togglePlay(input) {
|
togglePlay(input) {
|
||||||
// Toggle based on current state if nothing passed
|
// Toggle based on current state if nothing passed
|
||||||
@ -437,7 +438,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewind
|
* Rewind
|
||||||
* @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
|
* @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
|
||||||
*/
|
*/
|
||||||
rewind(seekTime) {
|
rewind(seekTime) {
|
||||||
this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime);
|
this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||||
@ -445,7 +446,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fast forward
|
* Fast forward
|
||||||
* @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
|
* @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
|
||||||
*/
|
*/
|
||||||
forward(seekTime) {
|
forward(seekTime) {
|
||||||
this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime);
|
this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||||
@ -453,7 +454,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Seek to a time
|
* Seek to a time
|
||||||
* @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
|
* @param {Number} input - where to seek to in seconds. Defaults to 0 (the start)
|
||||||
*/
|
*/
|
||||||
set currentTime(input) {
|
set currentTime(input) {
|
||||||
// Bail if media duration isn't available yet
|
// Bail if media duration isn't available yet
|
||||||
@ -512,7 +513,6 @@ class Plyr {
|
|||||||
get duration() {
|
get duration() {
|
||||||
// Faux duration set via config
|
// Faux duration set via config
|
||||||
const fauxDuration = parseFloat(this.config.duration);
|
const fauxDuration = parseFloat(this.config.duration);
|
||||||
|
|
||||||
// Media duration can be NaN or Infinity before the media has loaded
|
// Media duration can be NaN or Infinity before the media has loaded
|
||||||
const realDuration = (this.media || {}).duration;
|
const realDuration = (this.media || {}).duration;
|
||||||
const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration;
|
const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration;
|
||||||
@ -523,7 +523,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the player volume
|
* Set the player volume
|
||||||
* @param {number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
|
* @param {Number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
|
||||||
*/
|
*/
|
||||||
set volume(value) {
|
set volume(value) {
|
||||||
let volume = value;
|
let volume = value;
|
||||||
@ -574,7 +574,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Increase volume
|
* Increase volume
|
||||||
* @param {boolean} step - How much to decrease by (between 0 and 1)
|
* @param {Boolean} step - How much to decrease by (between 0 and 1)
|
||||||
*/
|
*/
|
||||||
increaseVolume(step) {
|
increaseVolume(step) {
|
||||||
const volume = this.media.muted ? 0 : this.volume;
|
const volume = this.media.muted ? 0 : this.volume;
|
||||||
@ -583,7 +583,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrease volume
|
* Decrease volume
|
||||||
* @param {boolean} step - How much to decrease by (between 0 and 1)
|
* @param {Boolean} step - How much to decrease by (between 0 and 1)
|
||||||
*/
|
*/
|
||||||
decreaseVolume(step) {
|
decreaseVolume(step) {
|
||||||
this.increaseVolume(-step);
|
this.increaseVolume(-step);
|
||||||
@ -591,7 +591,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set muted state
|
* Set muted state
|
||||||
* @param {boolean} mute
|
* @param {Boolean} mute
|
||||||
*/
|
*/
|
||||||
set muted(mute) {
|
set muted(mute) {
|
||||||
let toggle = mute;
|
let toggle = mute;
|
||||||
@ -643,7 +643,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback speed
|
* Set playback speed
|
||||||
* @param {number} speed - the speed of playback (0.5-2.0)
|
* @param {Number} speed - the speed of playback (0.5-2.0)
|
||||||
*/
|
*/
|
||||||
set speed(input) {
|
set speed(input) {
|
||||||
let speed = null;
|
let speed = null;
|
||||||
@ -660,24 +660,17 @@ class Plyr {
|
|||||||
speed = this.config.speed.selected;
|
speed = this.config.speed.selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set min/max
|
// Clamp to min/max
|
||||||
if (speed < 0.1) {
|
const { minimumSpeed: min, maximumSpeed: max } = this;
|
||||||
speed = 0.1;
|
speed = clamp(speed, min, max);
|
||||||
}
|
|
||||||
if (speed > 2.0) {
|
|
||||||
speed = 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.config.speed.options.includes(speed)) {
|
|
||||||
this.debug.warn(`Unsupported speed (${speed})`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update config
|
// Update config
|
||||||
this.config.speed.selected = speed;
|
this.config.speed.selected = speed;
|
||||||
|
|
||||||
// Set media speed
|
// Set media speed
|
||||||
|
setTimeout(() => {
|
||||||
this.media.playbackRate = speed;
|
this.media.playbackRate = speed;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -687,10 +680,46 @@ class Plyr {
|
|||||||
return Number(this.media.playbackRate);
|
return Number(this.media.playbackRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the minimum allowed speed
|
||||||
|
*/
|
||||||
|
get minimumSpeed() {
|
||||||
|
if (this.isYouTube) {
|
||||||
|
// https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
|
||||||
|
return Math.min(...this.options.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isVimeo) {
|
||||||
|
// https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/32320020/1191319
|
||||||
|
return 0.0625;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum allowed speed
|
||||||
|
*/
|
||||||
|
get maximumSpeed() {
|
||||||
|
if (this.isYouTube) {
|
||||||
|
// https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
|
||||||
|
return Math.max(...this.options.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isVimeo) {
|
||||||
|
// https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/32320020/1191319
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback quality
|
* Set playback quality
|
||||||
* Currently HTML5 & YouTube only
|
* Currently HTML5 & YouTube only
|
||||||
* @param {number} input - Quality level
|
* @param {Number} input - Quality level
|
||||||
*/
|
*/
|
||||||
set quality(input) {
|
set quality(input) {
|
||||||
const config = this.config.quality;
|
const config = this.config.quality;
|
||||||
@ -740,7 +769,7 @@ class Plyr {
|
|||||||
/**
|
/**
|
||||||
* Toggle loop
|
* Toggle loop
|
||||||
* TODO: Finish fancy new logic. Set the indicator on load as user may pass loop as config
|
* TODO: Finish fancy new logic. Set the indicator on load as user may pass loop as config
|
||||||
* @param {boolean} input - Whether to loop or not
|
* @param {Boolean} input - Whether to loop or not
|
||||||
*/
|
*/
|
||||||
set loop(input) {
|
set loop(input) {
|
||||||
const toggle = is.boolean(input) ? input : this.config.loop.active;
|
const toggle = is.boolean(input) ? input : this.config.loop.active;
|
||||||
@ -800,7 +829,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set new media source
|
* Set new media source
|
||||||
* @param {object} input - The new source object (see docs)
|
* @param {Object} input - The new source object (see docs)
|
||||||
*/
|
*/
|
||||||
set source(input) {
|
set source(input) {
|
||||||
source.change.call(this, input);
|
source.change.call(this, input);
|
||||||
@ -822,9 +851,22 @@ class Plyr {
|
|||||||
return is.url(download) ? download : this.source;
|
return is.url(download) ? download : this.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the download URL
|
||||||
|
*/
|
||||||
|
set download(input) {
|
||||||
|
if (!is.url(input)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.urls.download = input;
|
||||||
|
|
||||||
|
controls.setDownloadUrl.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the poster image for a video
|
* Set the poster image for a video
|
||||||
* @param {input} - the URL for the new poster image
|
* @param {String} input - the URL for the new poster image
|
||||||
*/
|
*/
|
||||||
set poster(input) {
|
set poster(input) {
|
||||||
if (!this.isVideo) {
|
if (!this.isVideo) {
|
||||||
@ -846,9 +888,41 @@ class Plyr {
|
|||||||
return this.media.getAttribute('poster');
|
return this.media.getAttribute('poster');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current aspect ratio in use
|
||||||
|
*/
|
||||||
|
get ratio() {
|
||||||
|
if (!this.isVideo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = reduceAspectRatio(getAspectRatio.call(this));
|
||||||
|
|
||||||
|
return is.array(ratio) ? ratio.join(':') : ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set video aspect ratio
|
||||||
|
*/
|
||||||
|
set ratio(input) {
|
||||||
|
if (!this.isVideo) {
|
||||||
|
this.debug.warn('Aspect ratio can only be set for video');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.string(input) || !validateRatio(input)) {
|
||||||
|
this.debug.error(`Invalid aspect ratio specified (${input})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.ratio = input;
|
||||||
|
|
||||||
|
setAspectRatio.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the autoplay state
|
* Set the autoplay state
|
||||||
* @param {boolean} input - Whether to autoplay or not
|
* @param {Boolean} input - Whether to autoplay or not
|
||||||
*/
|
*/
|
||||||
set autoplay(input) {
|
set autoplay(input) {
|
||||||
const toggle = is.boolean(input) ? input : this.config.autoplay;
|
const toggle = is.boolean(input) ? input : this.config.autoplay;
|
||||||
@ -864,7 +938,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle captions
|
* Toggle captions
|
||||||
* @param {boolean} input - Whether to enable captions
|
* @param {Boolean} input - Whether to enable captions
|
||||||
*/
|
*/
|
||||||
toggleCaptions(input) {
|
toggleCaptions(input) {
|
||||||
captions.toggle.call(this, input, false);
|
captions.toggle.call(this, input, false);
|
||||||
@ -872,7 +946,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the caption track by index
|
* Set the caption track by index
|
||||||
* @param {number} - Caption index
|
* @param {Number} - Caption index
|
||||||
*/
|
*/
|
||||||
set currentTrack(input) {
|
set currentTrack(input) {
|
||||||
captions.set.call(this, input, false);
|
captions.set.call(this, input, false);
|
||||||
@ -889,7 +963,7 @@ class Plyr {
|
|||||||
/**
|
/**
|
||||||
* Set the wanted language for captions
|
* Set the wanted language for captions
|
||||||
* Since tracks can be added later it won't update the actual caption track until there is a matching track
|
* Since tracks can be added later it won't update the actual caption track until there is a matching track
|
||||||
* @param {string} - Two character ISO language code (e.g. EN, FR, PT, etc)
|
* @param {String} - Two character ISO language code (e.g. EN, FR, PT, etc)
|
||||||
*/
|
*/
|
||||||
set language(input) {
|
set language(input) {
|
||||||
captions.setLanguage.call(this, input, false);
|
captions.setLanguage.call(this, input, false);
|
||||||
@ -962,17 +1036,15 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the player controls
|
* Toggle the player controls
|
||||||
* @param {boolean} [toggle] - Whether to show the controls
|
* @param {Boolean} [toggle] - Whether to show the controls
|
||||||
*/
|
*/
|
||||||
toggleControls(toggle) {
|
toggleControls(toggle) {
|
||||||
// Don't toggle if missing UI support or if it's audio
|
// Don't toggle if missing UI support or if it's audio
|
||||||
if (this.supported.ui && !this.isAudio) {
|
if (this.supported.ui && !this.isAudio) {
|
||||||
// Get state before change
|
// Get state before change
|
||||||
const isHidden = hasClass(this.elements.container, this.config.classNames.hideControls);
|
const isHidden = hasClass(this.elements.container, this.config.classNames.hideControls);
|
||||||
|
|
||||||
// Negate the argument if not undefined since adding the class to hides the controls
|
// Negate the argument if not undefined since adding the class to hides the controls
|
||||||
const force = typeof toggle === 'undefined' ? undefined : !toggle;
|
const force = typeof toggle === 'undefined' ? undefined : !toggle;
|
||||||
|
|
||||||
// Apply and get updated state
|
// Apply and get updated state
|
||||||
const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force);
|
const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force);
|
||||||
|
|
||||||
@ -995,8 +1067,8 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add event listeners
|
* Add event listeners
|
||||||
* @param {string} event - Event type
|
* @param {String} event - Event type
|
||||||
* @param {function} callback - Callback for when event occurs
|
* @param {Function} callback - Callback for when event occurs
|
||||||
*/
|
*/
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
on.call(this, this.elements.container, event, callback);
|
on.call(this, this.elements.container, event, callback);
|
||||||
@ -1004,8 +1076,8 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add event listeners once
|
* Add event listeners once
|
||||||
* @param {string} event - Event type
|
* @param {String} event - Event type
|
||||||
* @param {function} callback - Callback for when event occurs
|
* @param {Function} callback - Callback for when event occurs
|
||||||
*/
|
*/
|
||||||
once(event, callback) {
|
once(event, callback) {
|
||||||
once.call(this, this.elements.container, event, callback);
|
once.call(this, this.elements.container, event, callback);
|
||||||
@ -1013,8 +1085,8 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove event listeners
|
* Remove event listeners
|
||||||
* @param {string} event - Event type
|
* @param {String} event - Event type
|
||||||
* @param {function} callback - Callback for when event occurs
|
* @param {Function} callback - Callback for when event occurs
|
||||||
*/
|
*/
|
||||||
off(event, callback) {
|
off(event, callback) {
|
||||||
off(this.elements.container, event, callback);
|
off(this.elements.container, event, callback);
|
||||||
@ -1024,8 +1096,8 @@ class Plyr {
|
|||||||
* Destroy an instance
|
* Destroy an instance
|
||||||
* Event listeners are removed when elements are removed
|
* Event listeners are removed when elements are removed
|
||||||
* http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
|
* http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
|
||||||
* @param {function} callback - Callback for when destroy is complete
|
* @param {Function} callback - Callback for when destroy is complete
|
||||||
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
* @param {Boolean} soft - Whether it's a soft destroy (for source changes etc)
|
||||||
*/
|
*/
|
||||||
destroy(callback, soft = false) {
|
destroy(callback, soft = false) {
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
@ -1088,11 +1160,13 @@ class Plyr {
|
|||||||
// Stop playback
|
// Stop playback
|
||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
|
// Clear timeouts
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
clearTimeout(this.timers.controls);
|
||||||
|
clearTimeout(this.timers.resized);
|
||||||
|
|
||||||
// Provider specific stuff
|
// Provider specific stuff
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
// Clear timeout
|
|
||||||
clearTimeout(this.timers.loading);
|
|
||||||
|
|
||||||
// Restore native video controls
|
// Restore native video controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
@ -1124,7 +1198,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for support for a mime type (HTML5 only)
|
* Check for support for a mime type (HTML5 only)
|
||||||
* @param {string} type - Mime type
|
* @param {String} type - Mime type
|
||||||
*/
|
*/
|
||||||
supports(type) {
|
supports(type) {
|
||||||
return support.mime.call(this, type);
|
return support.mime.call(this, type);
|
||||||
@ -1132,9 +1206,9 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for support
|
* Check for support
|
||||||
* @param {string} type - Player type (audio/video)
|
* @param {String} type - Player type (audio/video)
|
||||||
* @param {string} provider - Provider (html5/youtube/vimeo)
|
* @param {String} provider - Provider (html5/youtube/vimeo)
|
||||||
* @param {bool} inline - Where player has `playsinline` sttribute
|
* @param {Boolean} inline - Where player has `playsinline` sttribute
|
||||||
*/
|
*/
|
||||||
static supported(type, provider, inline) {
|
static supported(type, provider, inline) {
|
||||||
return support.check(type, provider, inline);
|
return support.check(type, provider, inline);
|
||||||
@ -1142,8 +1216,8 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an SVG sprite into the page
|
* Load an SVG sprite into the page
|
||||||
* @param {string} url - URL for the SVG sprite
|
* @param {String} url - URL for the SVG sprite
|
||||||
* @param {string} [id] - Unique ID
|
* @param {String} [id] - Unique ID
|
||||||
*/
|
*/
|
||||||
static loadSprite(url, id) {
|
static loadSprite(url, id) {
|
||||||
return loadSprite(url, id);
|
return loadSprite(url, id);
|
||||||
@ -1152,7 +1226,7 @@ class Plyr {
|
|||||||
/**
|
/**
|
||||||
* Setup multiple instances
|
* Setup multiple instances
|
||||||
* @param {*} selector
|
* @param {*} selector
|
||||||
* @param {object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
static setup(selector, options = {}) {
|
static setup(selector, options = {}) {
|
||||||
let targets = null;
|
let targets = null;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.5.0-beta.5
|
// plyr.js v3.5.6
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import 'custom-event-polyfill';
|
import 'custom-event-polyfill';
|
||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
|
|
||||||
import Plyr from './plyr';
|
import Plyr from './plyr';
|
||||||
|
|
||||||
export default Plyr;
|
export default Plyr;
|
||||||
|
34
src/js/ui.js
34
src/js/ui.js
@ -10,7 +10,7 @@ import { getElement, toggleClass } from './utils/elements';
|
|||||||
import { ready, triggerEvent } from './utils/events';
|
import { ready, triggerEvent } from './utils/events';
|
||||||
import i18n from './utils/i18n';
|
import i18n from './utils/i18n';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import loadImage from './utils/loadImage';
|
import loadImage from './utils/load-image';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
@ -67,15 +67,15 @@ const ui = {
|
|||||||
// Reset mute state
|
// Reset mute state
|
||||||
this.muted = null;
|
this.muted = null;
|
||||||
|
|
||||||
// Reset speed
|
|
||||||
this.speed = null;
|
|
||||||
|
|
||||||
// Reset loop state
|
// Reset loop state
|
||||||
this.loop = null;
|
this.loop = null;
|
||||||
|
|
||||||
// Reset quality setting
|
// Reset quality setting
|
||||||
this.quality = null;
|
this.quality = null;
|
||||||
|
|
||||||
|
// Reset speed
|
||||||
|
this.speed = null;
|
||||||
|
|
||||||
// Reset volume display
|
// Reset volume display
|
||||||
controls.updateVolume.call(this);
|
controls.updateVolume.call(this);
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ const ui = {
|
|||||||
|
|
||||||
// Set state
|
// Set state
|
||||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||||
target.pressed = this.playing;
|
Object.assign(target, { pressed: this.playing });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only update controls on non timeupdate events
|
// Only update controls on non timeupdate events
|
||||||
@ -233,25 +233,37 @@ const ui = {
|
|||||||
clearTimeout(this.timers.loading);
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
// Timer to prevent flicker when seeking
|
// Timer to prevent flicker when seeking
|
||||||
this.timers.loading = setTimeout(() => {
|
this.timers.loading = setTimeout(
|
||||||
|
() => {
|
||||||
// Update progress bar loading class state
|
// Update progress bar loading class state
|
||||||
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
||||||
|
|
||||||
// Update controls visibility
|
// Update controls visibility
|
||||||
ui.toggleControls.call(this);
|
ui.toggleControls.call(this);
|
||||||
}, this.loading ? 250 : 0);
|
},
|
||||||
|
this.loading ? 250 : 0,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle controls based on state and `force` argument
|
// Toggle controls based on state and `force` argument
|
||||||
toggleControls(force) {
|
toggleControls(force) {
|
||||||
const { controls } = this.elements;
|
const { controls: controlsElement } = this.elements;
|
||||||
|
|
||||||
if (controls && this.config.hideControls) {
|
if (controlsElement && this.config.hideControls) {
|
||||||
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
||||||
const recentTouchSeek = (this.touch && this.lastSeekTime + 2000 > Date.now());
|
const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
|
||||||
|
|
||||||
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek));
|
this.toggleControls(
|
||||||
|
Boolean(
|
||||||
|
force ||
|
||||||
|
this.loading ||
|
||||||
|
this.paused ||
|
||||||
|
controlsElement.pressed ||
|
||||||
|
controlsElement.hover ||
|
||||||
|
recentTouchSeek,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Animation utils
|
// Animation utils
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import { toggleHidden } from './elements';
|
|
||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
export const transitionEndEvent = (() => {
|
export const transitionEndEvent = (() => {
|
||||||
@ -21,14 +20,19 @@ export const transitionEndEvent = (() => {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
export function repaint(element) {
|
export function repaint(element, delay) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
toggleHidden(element, true);
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.offsetHeight; // eslint-disable-line
|
element.hidden = true;
|
||||||
toggleHidden(element, false);
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
element.offsetHeight;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
element.hidden = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}, 0);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { toggleListener } from './events';
|
import { toggleListener } from './events';
|
||||||
import is from './is';
|
import is from './is';
|
||||||
|
import { extend } from './objects';
|
||||||
|
|
||||||
// Wrap an element
|
// Wrap an element
|
||||||
export function wrap(elements, wrapper) {
|
export function wrap(elements, wrapper) {
|
||||||
@ -16,7 +17,6 @@ export function wrap(elements, wrapper) {
|
|||||||
.reverse()
|
.reverse()
|
||||||
.forEach((element, index) => {
|
.forEach((element, index) => {
|
||||||
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
||||||
|
|
||||||
// Cache the current parent and sibling.
|
// Cache the current parent and sibling.
|
||||||
const parent = element.parentNode;
|
const parent = element.parentNode;
|
||||||
const sibling = element.nextSibling;
|
const sibling = element.nextSibling;
|
||||||
@ -137,30 +137,28 @@ export function getAttributesFromSelector(sel, existingAttributes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
const existing = existingAttributes;
|
const existing = extend({}, existingAttributes);
|
||||||
|
|
||||||
sel.split(',').forEach(s => {
|
sel.split(',').forEach(s => {
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
const selector = s.trim();
|
const selector = s.trim();
|
||||||
const className = selector.replace('.', '');
|
const className = selector.replace('.', '');
|
||||||
const stripped = selector.replace(/[[\]]/g, '');
|
const stripped = selector.replace(/[[\]]/g, '');
|
||||||
|
|
||||||
// Get the parts and value
|
// Get the parts and value
|
||||||
const parts = stripped.split('=');
|
const parts = stripped.split('=');
|
||||||
const key = parts[0];
|
const [key] = parts;
|
||||||
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
||||||
|
|
||||||
// Get the first character
|
// Get the first character
|
||||||
const start = selector.charAt(0);
|
const start = selector.charAt(0);
|
||||||
|
|
||||||
switch (start) {
|
switch (start) {
|
||||||
case '.':
|
case '.':
|
||||||
// Add to existing classname
|
// Add to existing classname
|
||||||
if (is.object(existing) && is.string(existing.class)) {
|
if (is.string(existing.class)) {
|
||||||
existing.class += ` ${className}`;
|
attributes.class = `${existing.class} ${className}`;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
attributes.class = className;
|
attributes.class = className;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '#':
|
case '#':
|
||||||
@ -179,7 +177,7 @@ export function getAttributesFromSelector(sel, existingAttributes) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return attributes;
|
return extend(existing, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle hidden
|
// Toggle hidden
|
||||||
@ -194,11 +192,8 @@ export function toggleHidden(element, hidden) {
|
|||||||
hide = !element.hidden;
|
hide = !element.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide) {
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.setAttribute('hidden', '');
|
element.hidden = hide;
|
||||||
} else {
|
|
||||||
element.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||||
@ -233,14 +228,14 @@ export function matches(element, selector) {
|
|||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches =
|
const method =
|
||||||
prototype.matches ||
|
prototype.matches ||
|
||||||
prototype.webkitMatchesSelector ||
|
prototype.webkitMatchesSelector ||
|
||||||
prototype.mozMatchesSelector ||
|
prototype.mozMatchesSelector ||
|
||||||
prototype.msMatchesSelector ||
|
prototype.msMatchesSelector ||
|
||||||
match;
|
match;
|
||||||
|
|
||||||
return matches.call(element, selector);
|
return method.call(element, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all elements
|
// Find all elements
|
||||||
|
@ -35,7 +35,6 @@ export function toggleListener(element, event, callback, toggle = false, passive
|
|||||||
|
|
||||||
// Allow multiple events
|
// Allow multiple events
|
||||||
const events = event.split(' ');
|
const events = event.split(' ');
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
// Default to just the capture boolean for browsers with no passive listener support
|
// Default to just the capture boolean for browsers with no passive listener support
|
||||||
let options = capture;
|
let options = capture;
|
||||||
|
@ -36,8 +36,8 @@ const i18n = {
|
|||||||
'{title}': config.title,
|
'{title}': config.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(replace).forEach(([key, value]) => {
|
Object.entries(replace).forEach(([k, v]) => {
|
||||||
string = replaceAll(string, key, value);
|
string = replaceAll(string, k, v);
|
||||||
});
|
});
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
|
@ -15,10 +15,10 @@ export default function loadSprite(url, id) {
|
|||||||
const prefix = 'cache';
|
const prefix = 'cache';
|
||||||
const hasId = is.string(id);
|
const hasId = is.string(id);
|
||||||
let isCached = false;
|
let isCached = false;
|
||||||
|
|
||||||
const exists = () => document.getElementById(id) !== null;
|
const exists = () => document.getElementById(id) !== null;
|
||||||
|
|
||||||
const update = (container, data) => {
|
const update = (container, data) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
container.innerHTML = data;
|
container.innerHTML = data;
|
||||||
|
|
||||||
// Check again incase of race condition
|
// Check again incase of race condition
|
||||||
@ -33,7 +33,6 @@ export default function loadSprite(url, id) {
|
|||||||
// Only load once if ID set
|
// Only load once if ID set
|
||||||
if (!hasId || !exists()) {
|
if (!hasId || !exists()) {
|
||||||
const useStorage = Storage.supported;
|
const useStorage = Storage.supported;
|
||||||
|
|
||||||
// Create container
|
// Create container
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.setAttribute('hidden', '');
|
container.setAttribute('hidden', '');
|
17
src/js/utils/numbers.js
Normal file
17
src/js/utils/numbers.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Returns a number whose value is limited to the given range.
|
||||||
|
*
|
||||||
|
* Example: limit the output of this computation to between 0 and 255
|
||||||
|
* (x * 255).clamp(0, 255)
|
||||||
|
*
|
||||||
|
* @param {Number} input
|
||||||
|
* @param {Number} min The lower boundary of the output range
|
||||||
|
* @param {Number} max The upper boundary of the output range
|
||||||
|
* @returns A number in the range [min, max]
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
export function clamp(input = 0, min = 0, max = 255) {
|
||||||
|
return Math.min(Math.max(input, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { clamp };
|
@ -4,26 +4,61 @@
|
|||||||
|
|
||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
/* function reduceAspectRatio(width, height) {
|
export function validateRatio(input) {
|
||||||
const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
|
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
|
||||||
const ratio = getRatio(width, height);
|
return false;
|
||||||
return `${width / ratio}:${height / ratio}`;
|
}
|
||||||
} */
|
|
||||||
|
|
||||||
// Set aspect ratio for responsive container
|
const ratio = is.array(input) ? input : input.split(':');
|
||||||
export function setAspectRatio(input) {
|
|
||||||
let ratio = input;
|
|
||||||
|
|
||||||
if (!is.string(ratio) && !is.nullOrUndefined(this.embed)) {
|
return ratio.map(Number).every(is.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reduceAspectRatio(ratio) {
|
||||||
|
if (!is.array(ratio) || !ratio.every(is.number)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [width, height] = ratio;
|
||||||
|
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
|
||||||
|
const divider = getDivider(width, height);
|
||||||
|
|
||||||
|
return [width / divider, height / divider];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAspectRatio(input) {
|
||||||
|
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
||||||
|
// Try provided ratio
|
||||||
|
let ratio = parse(input);
|
||||||
|
|
||||||
|
// Get from config
|
||||||
|
if (ratio === null) {
|
||||||
|
ratio = parse(this.config.ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get from embed
|
||||||
|
if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {
|
||||||
({ ratio } = this.embed);
|
({ ratio } = this.embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is.string(ratio)) {
|
// Get from HTML5 video
|
||||||
({ ratio } = this.config);
|
if (ratio === null && this.isHTML5) {
|
||||||
|
const { videoWidth, videoHeight } = this.media;
|
||||||
|
ratio = reduceAspectRatio([videoWidth, videoHeight]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x, y] = ratio.split(':').map(Number);
|
return ratio;
|
||||||
const padding = (100 / x) * y;
|
}
|
||||||
|
|
||||||
|
// Set aspect ratio for responsive container
|
||||||
|
export function setAspectRatio(input) {
|
||||||
|
if (!this.isVideo) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = getAspectRatio.call(this, input);
|
||||||
|
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
||||||
|
const padding = (100 / w) * h;
|
||||||
|
|
||||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||||
|
|
||||||
@ -32,6 +67,8 @@ export function setAspectRatio(input) {
|
|||||||
const height = 240;
|
const height = 240;
|
||||||
const offset = (height - padding) / (height / 50);
|
const offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = `translateY(-${offset}%)`;
|
this.media.style.transform = `translateY(-${offset}%)`;
|
||||||
|
} else if (this.isHTML5) {
|
||||||
|
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { padding, ratio };
|
return { padding, ratio };
|
||||||
|
@ -18,7 +18,6 @@ export function formatTime(time = 0, displayHours = false, inverted = false) {
|
|||||||
|
|
||||||
// Format time component to add leading zero
|
// Format time component to add leading zero
|
||||||
const format = value => `0${value}`.slice(-2);
|
const format = value => `0${value}`.slice(-2);
|
||||||
|
|
||||||
// Breakdown to hours, mins, secs
|
// Breakdown to hours, mins, secs
|
||||||
let hours = getHours(time);
|
let hours = getHours(time);
|
||||||
const mins = getMinutes(time);
|
const mins = getMinutes(time);
|
||||||
|
@ -6,8 +6,8 @@ import is from './is';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string to a URL object
|
* Parse a string to a URL object
|
||||||
* @param {string} input - the URL to be parsed
|
* @param {String} input - the URL to be parsed
|
||||||
* @param {boolean} safe - failsafe parsing
|
* @param {Boolean} safe - failsafe parsing
|
||||||
*/
|
*/
|
||||||
export function parseUrl(input, safe = true) {
|
export function parseUrl(input, safe = true) {
|
||||||
let url = input;
|
let url = input;
|
||||||
|
@ -63,10 +63,6 @@ a.plyr__control {
|
|||||||
|
|
||||||
// Video control
|
// Video control
|
||||||
.plyr--video .plyr__control {
|
.plyr--video .plyr__control {
|
||||||
svg {
|
|
||||||
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hover and tab focus
|
// Hover and tab focus
|
||||||
&.plyr__tab-focus,
|
&.plyr__tab-focus,
|
||||||
&:hover,
|
&:hover,
|
||||||
@ -81,7 +77,6 @@ a.plyr__control {
|
|||||||
background: rgba($plyr-video-control-bg-hover, 0.8);
|
background: rgba($plyr-video-control-bg-hover, 0.8);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.15);
|
|
||||||
color: $plyr-video-control-color;
|
color: $plyr-video-control-color;
|
||||||
display: none;
|
display: none;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -14,42 +14,47 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
.plyr__progress__container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; // Fix for Edge issue where content would overflow
|
||||||
|
}
|
||||||
|
|
||||||
// Spacing
|
// Spacing
|
||||||
> .plyr__control,
|
.plyr__controls__item {
|
||||||
.plyr__progress,
|
margin-left: ($plyr-control-spacing / 4);
|
||||||
.plyr__time,
|
|
||||||
.plyr__menu,
|
|
||||||
.plyr__volume {
|
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plyr__menu + .plyr__control,
|
&:first-child {
|
||||||
> .plyr__control + .plyr__menu,
|
|
||||||
> .plyr__control + .plyr__control,
|
|
||||||
.plyr__progress + .plyr__control {
|
|
||||||
margin-left: floor($plyr-control-spacing / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .plyr__control:first-child,
|
|
||||||
> .plyr__control:first-child + [data-plyr='pause'] {
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.plyr__progress__container {
|
||||||
|
padding-left: ($plyr-control-spacing / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__time {
|
||||||
|
padding: 0 ($plyr-control-spacing / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__progress__container:first-child,
|
||||||
|
&.plyr__time:first-child,
|
||||||
|
&.plyr__time + .plyr__time {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__volume {
|
||||||
|
padding-right: ($plyr-control-spacing / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__volume:first-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hide empty controls
|
// Hide empty controls
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-sm) {
|
|
||||||
> .plyr__control,
|
|
||||||
.plyr__menu,
|
|
||||||
.plyr__progress,
|
|
||||||
.plyr__time,
|
|
||||||
.plyr__volume {
|
|
||||||
margin-left: $plyr-control-spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio controls
|
// Audio controls
|
||||||
@ -62,10 +67,7 @@
|
|||||||
|
|
||||||
// Video controls
|
// Video controls
|
||||||
.plyr--video .plyr__controls {
|
.plyr--video .plyr__controls {
|
||||||
background: linear-gradient(
|
background: linear-gradient(rgba($plyr-video-controls-bg, 0), rgba($plyr-video-controls-bg, 0.7));
|
||||||
rgba($plyr-video-controls-bg, 0),
|
|
||||||
rgba($plyr-video-controls-bg, 0.7)
|
|
||||||
);
|
|
||||||
border-bottom-left-radius: inherit;
|
border-bottom-left-radius: inherit;
|
||||||
border-bottom-right-radius: inherit;
|
border-bottom-right-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
// --------------------------------------------------------------
|
|
||||||
// Embedded players
|
|
||||||
// YouTube, Vimeo, etc
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
|
|
||||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
|
||||||
$embed-padding: ((100 / 16) * 9);
|
|
||||||
|
|
||||||
.plyr__video-embed {
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: to-percentage($embed-padding);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border: 0;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
user-select: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the full custom UI is supported
|
|
||||||
.plyr--full-ui .plyr__video-embed {
|
|
||||||
$height: 240;
|
|
||||||
$offset: to-percentage(($height - $embed-padding) / ($height / 50));
|
|
||||||
|
|
||||||
// Only used for Vimeo
|
|
||||||
> .plyr__video-embed__container {
|
|
||||||
padding-bottom: to-percentage($height);
|
|
||||||
position: relative;
|
|
||||||
transform: translateY(-$offset);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,18 +2,19 @@
|
|||||||
// Playback progress
|
// Playback progress
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Offset the range thumb in order to be able to calculate the relative progress (#954)
|
||||||
|
$plyr-progress-offset: $plyr-range-thumb-height;
|
||||||
|
|
||||||
.plyr__progress {
|
.plyr__progress {
|
||||||
flex: 1;
|
left: $plyr-progress-offset / 2;
|
||||||
left: $plyr-range-thumb-height / 2;
|
margin-right: $plyr-progress-offset;
|
||||||
margin-right: $plyr-range-thumb-height;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
input[type='range'],
|
input[type='range'],
|
||||||
&__buffer {
|
&__buffer {
|
||||||
margin-left: -($plyr-range-thumb-height / 2);
|
margin-left: -($plyr-progress-offset / 2);
|
||||||
margin-right: -($plyr-range-thumb-height / 2);
|
margin-right: -($plyr-progress-offset / 2);
|
||||||
// Offset the range thumb in order to be able to calculate the relative progress (#954)
|
width: calc(100% + #{$plyr-progress-offset});
|
||||||
width: calc(100% + #{$plyr-range-thumb-height});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
|
@ -20,3 +20,36 @@
|
|||||||
// Require z-index to force border-radius
|
// Require z-index to force border-radius
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||||
|
$embed-padding: ((100 / 16) * 9);
|
||||||
|
|
||||||
|
.plyr__video-embed,
|
||||||
|
.plyr__video-wrapper--fixed-ratio {
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: to-percentage($embed-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plyr__video-embed iframe,
|
||||||
|
.plyr__video-wrapper--fixed-ratio video {
|
||||||
|
border: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
user-select: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the full custom UI is supported
|
||||||
|
.plyr--full-ui .plyr__video-embed {
|
||||||
|
$height: 240;
|
||||||
|
$offset: to-percentage(($height - $embed-padding) / ($height / 50));
|
||||||
|
|
||||||
|
// Only used for Vimeo
|
||||||
|
> .plyr__video-embed__container {
|
||||||
|
padding-bottom: to-percentage($height);
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(-$offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -62,12 +62,13 @@
|
|||||||
|
|
||||||
.plyr__video-wrapper {
|
.plyr__video-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vimeo requires some different styling
|
// Vimeo requires some different styling
|
||||||
&.plyr--vimeo .plyr__video-wrapper {
|
&.plyr--vimeo .plyr__video-wrapper {
|
||||||
height: 0;
|
height: 0;
|
||||||
|
position: relative;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
// The countdown label
|
// The countdown label
|
||||||
&::after {
|
&::after {
|
||||||
background: rgba($plyr-color-gunmetal, 0.8);
|
background: rgba($plyr-color-gray-9, 0.8);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
bottom: $plyr-control-spacing;
|
bottom: $plyr-control-spacing;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -7,7 +7,7 @@ $plyr-preview-bg: $plyr-tooltip-bg !default;
|
|||||||
$plyr-preview-radius: $plyr-tooltip-radius !default;
|
$plyr-preview-radius: $plyr-tooltip-radius !default;
|
||||||
$plyr-preview-shadow: $plyr-tooltip-shadow !default;
|
$plyr-preview-shadow: $plyr-tooltip-shadow !default;
|
||||||
$plyr-preview-arrow-size: $plyr-tooltip-arrow-size !default;
|
$plyr-preview-arrow-size: $plyr-tooltip-arrow-size !default;
|
||||||
$plyr-preview-image-bg: $plyr-color-heather !default;
|
$plyr-preview-image-bg: $plyr-color-gray-2 !default;
|
||||||
$plyr-preview-time-font-size: $plyr-font-size-time !default;
|
$plyr-preview-time-font-size: $plyr-font-size-time !default;
|
||||||
$plyr-preview-time-padding: 3px 6px !default;
|
$plyr-preview-time-padding: 3px 6px !default;
|
||||||
$plyr-preview-time-bg: rgba(0, 0, 0, 0.55);
|
$plyr-preview-time-bg: rgba(0, 0, 0, 0.55);
|
@ -29,7 +29,6 @@
|
|||||||
@import 'components/captions';
|
@import 'components/captions';
|
||||||
@import 'components/control';
|
@import 'components/control';
|
||||||
@import 'components/controls';
|
@import 'components/controls';
|
||||||
@import 'components/embed';
|
|
||||||
@import 'components/menus';
|
@import 'components/menus';
|
||||||
@import 'components/sliders';
|
@import 'components/sliders';
|
||||||
@import 'components/poster';
|
@import 'components/poster';
|
||||||
@ -42,7 +41,7 @@
|
|||||||
@import 'states/fullscreen';
|
@import 'states/fullscreen';
|
||||||
|
|
||||||
@import 'plugins/ads';
|
@import 'plugins/ads';
|
||||||
@import 'plugins/previewThumbnails';
|
@import 'plugins/preview-thumbnails';
|
||||||
|
|
||||||
@import 'utils/animation';
|
@import 'utils/animation';
|
||||||
@import 'utils/hidden';
|
@import 'utils/hidden';
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
// Badges
|
// Badges
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-badge-bg: $plyr-color-fiord !default;
|
$plyr-badge-bg: $plyr-color-gray-7 !default;
|
||||||
$plyr-badge-color: #fff !default;
|
$plyr-badge-color: #fff !default;
|
||||||
|
@ -2,8 +2,16 @@
|
|||||||
// Colors
|
// Colors
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-color-main: #1aafff !default;
|
$plyr-color-main: hsl(198, 100%, 50%) !default;
|
||||||
$plyr-color-gunmetal: #2f343d !default;
|
|
||||||
$plyr-color-fiord: #4f5b5f !default;
|
// Grayscale
|
||||||
$plyr-color-lynch: #6b7d85 !default;
|
$plyr-color-gray-9: hsl(210, 15%, 16%);
|
||||||
$plyr-color-heather: #b7c5cd !default;
|
$plyr-color-gray-8: lighten($plyr-color-gray-9, 9%);
|
||||||
|
$plyr-color-gray-7: lighten($plyr-color-gray-8, 9%);
|
||||||
|
$plyr-color-gray-6: lighten($plyr-color-gray-7, 9%);
|
||||||
|
$plyr-color-gray-5: lighten($plyr-color-gray-6, 9%);
|
||||||
|
$plyr-color-gray-4: lighten($plyr-color-gray-5, 9%);
|
||||||
|
$plyr-color-gray-3: lighten($plyr-color-gray-4, 9%);
|
||||||
|
$plyr-color-gray-2: lighten($plyr-color-gray-3, 9%);
|
||||||
|
$plyr-color-gray-1: lighten($plyr-color-gray-2, 9%);
|
||||||
|
$plyr-color-gray-0: lighten($plyr-color-gray-1, 9%);
|
||||||
|
@ -13,6 +13,6 @@ $plyr-video-control-color-hover: #fff !default;
|
|||||||
$plyr-video-control-bg-hover: $plyr-color-main !default;
|
$plyr-video-control-bg-hover: $plyr-color-main !default;
|
||||||
|
|
||||||
$plyr-audio-controls-bg: #fff !default;
|
$plyr-audio-controls-bg: #fff !default;
|
||||||
$plyr-audio-control-color: $plyr-color-fiord !default;
|
$plyr-audio-control-color: $plyr-color-gray-7 !default;
|
||||||
$plyr-audio-control-color-hover: #fff !default;
|
$plyr-audio-control-color-hover: #fff !default;
|
||||||
$plyr-audio-control-bg-hover: $plyr-color-main !default;
|
$plyr-audio-control-bg-hover: $plyr-color-main !default;
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-menu-bg: rgba(#fff, 0.9) !default;
|
$plyr-menu-bg: rgba(#fff, 0.9) !default;
|
||||||
$plyr-menu-color: $plyr-color-fiord !default;
|
$plyr-menu-color: $plyr-color-gray-7 !default;
|
||||||
$plyr-menu-arrow-size: 6px !default;
|
$plyr-menu-arrow-size: 6px !default;
|
||||||
$plyr-menu-border-color: $plyr-color-heather !default;
|
$plyr-menu-border-color: $plyr-color-gray-2 !default;
|
||||||
$plyr-menu-border-shadow-color: #fff !default;
|
$plyr-menu-border-shadow-color: #fff !default;
|
||||||
$plyr-menu-shadow: 0 1px 2px rgba(#000, 0.15) !default;
|
$plyr-menu-shadow: 0 1px 2px rgba(#000, 0.15) !default;
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
$plyr-progress-loading-size: 25px !default;
|
$plyr-progress-loading-size: 25px !default;
|
||||||
$plyr-progress-loading-bg: rgba($plyr-color-gunmetal, 0.6) !default;
|
$plyr-progress-loading-bg: rgba($plyr-color-gray-9, 0.6) !default;
|
||||||
|
|
||||||
// Buffered
|
// Buffered
|
||||||
$plyr-video-progress-buffered-bg: rgba(#fff, 0.25) !default;
|
$plyr-video-progress-buffered-bg: rgba(#fff, 0.25) !default;
|
||||||
$plyr-audio-progress-buffered-bg: rgba($plyr-color-heather, 0.66) !default;
|
$plyr-audio-progress-buffered-bg: rgba($plyr-color-gray-2, 0.66) !default;
|
||||||
|
@ -9,7 +9,7 @@ $plyr-range-thumb-active-shadow-width: 3px !default;
|
|||||||
$plyr-range-thumb-height: 13px !default;
|
$plyr-range-thumb-height: 13px !default;
|
||||||
$plyr-range-thumb-bg: #fff !default;
|
$plyr-range-thumb-bg: #fff !default;
|
||||||
$plyr-range-thumb-border: 2px solid transparent !default;
|
$plyr-range-thumb-border: 2px solid transparent !default;
|
||||||
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gray-9, 0.2) !default;
|
||||||
|
|
||||||
// Track
|
// Track
|
||||||
$plyr-range-track-height: 5px !default;
|
$plyr-range-track-height: 5px !default;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-tooltip-bg: rgba(#fff, 0.9) !default;
|
$plyr-tooltip-bg: rgba(#fff, 0.9) !default;
|
||||||
$plyr-tooltip-color: $plyr-color-fiord !default;
|
$plyr-tooltip-color: $plyr-color-gray-7 !default;
|
||||||
$plyr-tooltip-padding: ($plyr-control-spacing / 2) !default;
|
$plyr-tooltip-padding: ($plyr-control-spacing / 2) !default;
|
||||||
$plyr-tooltip-arrow-size: 4px !default;
|
$plyr-tooltip-arrow-size: 4px !default;
|
||||||
$plyr-tooltip-radius: 3px !default;
|
$plyr-tooltip-radius: 3px !default;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user