Merge branch 'develop' into css-variables
# Conflicts: # demo/dist/demo.css # demo/dist/demo.min.js.map # demo/index.html # dist/plyr.css # dist/plyr.min.js.map # dist/plyr.min.mjs.map # dist/plyr.polyfilled.min.js.map # dist/plyr.polyfilled.min.mjs.map # gulpfile.js # src/sass/base.scss # src/sass/components/control.scss # src/sass/settings/colors.scss # src/sass/settings/controls.scss
This commit is contained in:
commit
da943b384c
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: sampotts
|
||||
patreon: plyr
|
||||
open_collective: plyr
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,9 +3,7 @@ node_modules
|
||||
credentials.json
|
||||
*.mp4
|
||||
!dist/blank.mp4
|
||||
index-*.html
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
*.webm
|
||||
.idea/
|
||||
|
6
.gitpod.yml
Normal file
6
.gitpod.yml
Normal file
@ -0,0 +1,6 @@
|
||||
tasks:
|
||||
- init: npm install && npm i gulp -g
|
||||
command: gulp
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: open-preview
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -2,11 +2,11 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
"dbaeumer.vscode-eslint",
|
||||
"wix.vscode-import-cost",
|
||||
"esbenp.prettier-vscode",
|
||||
"shinnn.stylelint",
|
||||
"wayou.vscode-todo-highlight"
|
||||
"wayou.vscode-todo-highlight",
|
||||
"wix.vscode-import-cost",
|
||||
"stylelint.vscode-stylelint",
|
||||
"pflannery.vscode-versionlens"
|
||||
]
|
||||
}
|
||||
|
@ -10,13 +10,15 @@
|
||||
"src": "./src/js/plyr.polyfilled.js",
|
||||
"dist": "./dist/",
|
||||
"formats": ["es", "umd"],
|
||||
"namespace": "Plyr"
|
||||
"namespace": "Plyr",
|
||||
"polyfill": true
|
||||
},
|
||||
"demo.js": {
|
||||
"src": "./demo/src/js/demo.js",
|
||||
"dist": "./demo/dist/",
|
||||
"formats": ["iife"],
|
||||
"namespace": "Demo"
|
||||
"namespace": "Demo",
|
||||
"polyfill": true
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
|
70
changelog.md
70
changelog.md
@ -1,4 +1,62 @@
|
||||
## v3.5.4
|
||||
### v3.5.10
|
||||
|
||||
- iOS volume display fix
|
||||
|
||||
### v3.5.9
|
||||
|
||||
- Fix for regression with volume control width
|
||||
- Ensure poster image is not downloaded again for HTML5 videos
|
||||
|
||||
### v3.5.8
|
||||
|
||||
- Added `flex-direction` property to fix some issues introduced in v3.5.7 when using custom CSS
|
||||
- Cleaned up the organisation of some of the SCSS files (should not effect CSS output)
|
||||
- Added `referrerPolicy` option for Vimeo to prevent an issue present in the demo site
|
||||
- Remove all Vimeo controls for Pro & Premium accounts
|
||||
- Improve thumbnail size calculations when size is set per css (thanks @ydylla)
|
||||
- Add previewThumbnails source setter (thanks @ydylla)
|
||||
- More speed setting logic improvements
|
||||
|
||||
### v3.5.7
|
||||
|
||||
- Typescript typings (thanks @ondratra)
|
||||
- `togglePlay` now also returns a `Promise` (thanks @azizhk)
|
||||
- Documentation improvements and typo fixes (thanks @ffpetrovic, @skerbis, @ayunami2000, @pjbaert, @MaxGiting, @0xflotus and @thatrobotdev)
|
||||
- Accessibility tweak for the play button (thanks @lunika)
|
||||
- Fix for ads configuration (thanks @SoftCreatR)
|
||||
- Fix handling listener return value (thanks @taion)
|
||||
- Added localisation key for PIP (picture-in-picture) (thanks @lmislm)
|
||||
- Preserve viewBox attribute in SVG sprite symbols (thanks @bseib)
|
||||
- Fix being unable to unmute autoplayed video on iOS (thanks @sumanbh)
|
||||
- Fixed Plyr container not resizing responsively (thanks @shravan2x)
|
||||
- Change vimeo demo video (thanks @thatrobotdev)
|
||||
- Fix for `Uncaught RangeError: Maximum call stack size exceeded` (thanks @laukstein)
|
||||
- Improve fullscreen experience on some devices (thanks @savroff)
|
||||
- Improvements to buffering state for embedded players (thanks @doostinharrell)
|
||||
- Prevents IE11 with resetOnEnd option set to true to play video again (thanks @Felipe K. De Boni)
|
||||
- Fix for multiple poster image downloads (use the native poster only for HTML5 videos)
|
||||
- Various presentational fixes
|
||||
- Removed logic to hide/show volume controls based on audio track detection due to it's problematic nature. If you want to hide volume control, use the `controls` option to do so.
|
||||
- Fix preview thumbnail scrubbing not working on mobile touch devices (thanks @ydylla)
|
||||
- Add download attribute to download button (thanks @Code1110)
|
||||
- Trap keyboard focus only when fullscreen (thanks @k-jensen)
|
||||
- Improvements to speed options - you can now specify all options in the UI (YouTube and Vimeo only accept 0.5-2) (thanks @ydylla)
|
||||
- Improve/fix quality change state restoring (thanks @ydylla)
|
||||
|
||||
_Note:_ This update contains CSS changes.
|
||||
|
||||
### 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
|
||||
@ -11,7 +69,7 @@
|
||||
- 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
|
||||
### 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
|
||||
@ -19,11 +77,11 @@
|
||||
- Fix: Allow absolute paths in preview thumbnails
|
||||
- Improvement: Allow optional hours and ms in VTT parser in preview thumbnails
|
||||
|
||||
## v3.5.2
|
||||
### v3.5.2
|
||||
|
||||
- Fixed issue where the preview thumbnail was present while scrubbing
|
||||
|
||||
## v3.5.1
|
||||
### v3.5.1
|
||||
|
||||
- Fixed build issues with babel and browserslist
|
||||
|
||||
@ -153,7 +211,9 @@ Thanks @friday for the following:
|
||||
- Suggestion: Remove array newline rule
|
||||
- Contributions improvements
|
||||
|
||||
- fix: html5.cancelRequest not remove source tag correctly (thanks @a60814billy)
|
||||
and other folks for:
|
||||
|
||||
- Fix: html5.cancelRequest not remove source tag correctly (thanks @a60814billy)
|
||||
- remove event listeners in destroy() (thanks @cky917)
|
||||
- Fix markdown in README (thanks @azu)
|
||||
- Some parts of the accessibility improvements outlined in #905 (more on the way...)
|
||||
|
@ -27,7 +27,13 @@ Please follow the instructions in our issue templates. Don't use github issues t
|
||||
|
||||
* Fork Plyr, and create a new branch in your fork, based on the **develop** branch
|
||||
|
||||
* To test locally, you can use the demo. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build while you are working, and run a local server from the repository root directory. If you have Python installed, this command should work: `python -m SimpleHTTPServer 8080`. Then go to `http://localhost:8080/demo/`
|
||||
* To test locally, you can use the demo site. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build and it will run a local web server for development and watch for any changes.
|
||||
|
||||
### Online one-click setup
|
||||
|
||||
Alternatively can also use Gitpod, a free online Visual Studio Code-like IDE. With a single click it will automatically launch a ready-to-code workspace with all the dependencies pre-installed, gulp watching for changes and the web server running, so that you can start coding straightaway.
|
||||
|
||||
[](https://gitpod.io/from-referrer/)
|
||||
|
||||
* Develop and test your modifications.
|
||||
|
||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
16373
demo/dist/demo.js
vendored
16373
demo/dist/demo.js
vendored
File diff suppressed because it is too large
Load Diff
4
demo/dist/demo.min.js
vendored
4
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/demo.svg
vendored
2
demo/dist/demo.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.6 KiB |
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="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
||||
|
||||
<!-- Opengraph -->
|
||||
<!-- Open Graph -->
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
||||
@ -66,7 +66,7 @@
|
||||
<body>
|
||||
<div class="grid">
|
||||
<header>
|
||||
<h1>Plyr</h1>
|
||||
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
||||
<p>
|
||||
A simple, accessible and customisable media player for
|
||||
<button type="button" class="faux-link" data-source="video">
|
||||
@ -109,7 +109,7 @@
|
||||
</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">
|
||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
||||
<span class="sr-only">ai.vi</span>
|
||||
@ -117,27 +117,25 @@
|
||||
</p>
|
||||
|
||||
<div class="call-to-action">
|
||||
<span class="button--with-count">
|
||||
<a <a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr-button">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>GitHub</title>
|
||||
<path
|
||||
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>GitHub</title>
|
||||
<path
|
||||
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"
|
||||
></path>
|
||||
</svg>
|
||||
Download on GitHub
|
||||
</a>
|
||||
</span>
|
||||
></path>
|
||||
</svg>
|
||||
Download on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<!-- style="--plyr-color-main: #47bb4d; --plyr-video-control-bg-hover: var(--plyr-color-main); " -->
|
||||
<div id="container">
|
||||
<!-- style="--plyr-color-main: #47bb4d; --plyr-video-control-background-hover: var(--plyr-color-main); " -->
|
||||
<div id="container" style="--plyr-color-main: #1ac266;">
|
||||
<video
|
||||
controls
|
||||
crossorigin
|
||||
@ -235,7 +233,8 @@
|
||||
</li>
|
||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||
<small>
|
||||
<a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on
|
||||
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
||||
on
|
||||
<span class="color--vimeo">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>Vimeo</title>
|
||||
@ -267,23 +266,13 @@
|
||||
<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"
|
||||
target="_blank"
|
||||
class="js-shr-button"
|
||||
class="js-shr"
|
||||
>tweet it</a
|
||||
>
|
||||
👍
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script
|
||||
src="https://cdn.polyfill.io/v3/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values,URL,Math.trunc&flags=gated"
|
||||
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>
|
||||
</body>
|
||||
</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.6.4",
|
||||
"custom-event-polyfill": "^1.0.7",
|
||||
"raven-js": "^3.27.2",
|
||||
"shr-buttons": "2.0.3",
|
||||
"url-polyfill": "^1.1.8"
|
||||
}
|
||||
}
|
@ -4,9 +4,16 @@
|
||||
// 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 Shr from 'shr-buttons';
|
||||
|
||||
import Plyr from '../../../src/js/plyr';
|
||||
import sources from './sources';
|
||||
import toggleClass from './toggle-class';
|
||||
|
||||
(() => {
|
||||
const { host } = window.location;
|
||||
@ -18,45 +25,15 @@ import Plyr from '../../../src/js/plyr';
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
Raven.context(() => {
|
||||
const selector = '#player';
|
||||
const container = document.getElementById('container');
|
||||
|
||||
if (window.Shr) {
|
||||
window.Shr.setup('.js-shr-button', {
|
||||
count: {
|
||||
classname: 'button__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 share buttons
|
||||
Shr.setup('.js-shr', {
|
||||
count: {
|
||||
className: 'button__count',
|
||||
},
|
||||
wrapper: {
|
||||
className: 'button--with-count',
|
||||
},
|
||||
});
|
||||
|
||||
// Setup the player
|
||||
@ -73,9 +50,6 @@ import Plyr from '../../../src/js/plyr';
|
||||
captions: {
|
||||
active: true,
|
||||
},
|
||||
keys: {
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||
},
|
||||
ads: {
|
||||
enabled: env.prod || env.dev,
|
||||
publisherId: '918848828995742',
|
||||
@ -87,6 +61,10 @@ import Plyr from '../../../src/js/plyr';
|
||||
'https://cdn.plyr.io/static/demo/thumbs/240p.vtt',
|
||||
],
|
||||
},
|
||||
vimeo: {
|
||||
// Prevent Vimeo blocking plyr.io demo site
|
||||
referrerPolicy: 'no-referrer',
|
||||
},
|
||||
});
|
||||
|
||||
// Expose for tinkering in the console
|
||||
@ -94,131 +72,12 @@ import Plyr from '../../../src/js/plyr';
|
||||
|
||||
// Setup type toggle
|
||||
const buttons = document.querySelectorAll('[data-source]');
|
||||
const types = {
|
||||
video: 'video',
|
||||
audio: 'audio',
|
||||
youtube: 'youtube',
|
||||
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;
|
||||
const types = Object.keys(sources);
|
||||
const historySupport = Boolean(window.history && window.history.pushState);
|
||||
let currentType = window.location.hash.substring(1);
|
||||
const hasCurrentType = !currentType.length;
|
||||
|
||||
function render(type) {
|
||||
// Remove active classes
|
||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||
|
||||
@ -227,9 +86,31 @@ import Plyr from '../../../src/js/plyr';
|
||||
|
||||
// Show 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
|
||||
@ -237,7 +118,7 @@ import Plyr from '../../../src/js/plyr';
|
||||
button.addEventListener('click', () => {
|
||||
const type = button.getAttribute('data-source');
|
||||
|
||||
newSource(type);
|
||||
setSource(type);
|
||||
|
||||
if (historySupport) {
|
||||
window.history.pushState({ type }, '', `#${type}`);
|
||||
@ -247,36 +128,27 @@ import Plyr from '../../../src/js/plyr';
|
||||
|
||||
// List for backwards/forwards
|
||||
window.addEventListener('popstate', event => {
|
||||
if (event.state && 'type' in event.state) {
|
||||
newSource(event.state.type);
|
||||
if (event.state && Object.keys(event.state).includes('type')) {
|
||||
setSource(event.state.type);
|
||||
}
|
||||
});
|
||||
|
||||
// On load
|
||||
if (historySupport) {
|
||||
const video = !currentType.length;
|
||||
|
||||
// If there's no current type set, assume video
|
||||
if (video) {
|
||||
currentType = types.video;
|
||||
}
|
||||
|
||||
// Replace current history state
|
||||
if (currentType in types) {
|
||||
window.history.replaceState(
|
||||
{
|
||||
type: currentType,
|
||||
},
|
||||
'',
|
||||
video ? '' : `#${currentType}`,
|
||||
);
|
||||
}
|
||||
|
||||
// If it's not video, load the source
|
||||
if (currentType !== types.video) {
|
||||
newSource(currentType, true);
|
||||
}
|
||||
// If there's no current type set, assume video
|
||||
if (hasCurrentType) {
|
||||
currentType = 'video';
|
||||
}
|
||||
|
||||
// Replace current history state
|
||||
if (historySupport && types.includes(currentType)) {
|
||||
window.history.replaceState({ type: currentType }, '', hasCurrentType ? '' : `#${currentType}`);
|
||||
}
|
||||
|
||||
// If it's not video, load the source
|
||||
if (currentType !== 'video') {
|
||||
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/40648169',
|
||||
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__count {
|
||||
align-items: center;
|
||||
background: $color-button-background;
|
||||
border: 0;
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||
color: $color-button-text;
|
||||
display: inline-flex;
|
||||
padding: ($spacing-base * 0.75);
|
||||
position: relative;
|
||||
@ -21,14 +19,16 @@
|
||||
|
||||
// Buttons
|
||||
.button {
|
||||
background: $color-button-background;
|
||||
color: $color-button-text;
|
||||
font-weight: $font-weight-bold;
|
||||
padding-left: $spacing-base;
|
||||
padding-right: $spacing-base;
|
||||
padding-left: ($spacing-base * 1.25);
|
||||
padding-right: ($spacing-base * 1.25);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $gray-dark;
|
||||
background: $color-button-background-hover;
|
||||
|
||||
// Remove the underline/border
|
||||
&::after {
|
||||
@ -65,12 +65,14 @@
|
||||
// Count bubble
|
||||
.button__count {
|
||||
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 {
|
||||
border: $arrow-size solid transparent;
|
||||
border-left-width: 0;
|
||||
border-right-color: $color-button-background;
|
||||
border-right-color: $color-button-count-background;
|
||||
content: '';
|
||||
height: 0;
|
||||
position: absolute;
|
||||
|
@ -6,6 +6,13 @@ header {
|
||||
padding-bottom: $spacing-base;
|
||||
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 {
|
||||
margin-top: ($spacing-base * 1.5);
|
||||
}
|
||||
@ -15,5 +22,9 @@ header {
|
||||
max-width: 360px;
|
||||
padding-bottom: ($spacing-base * 2);
|
||||
text-align: left;
|
||||
|
||||
p:first-of-type {
|
||||
@include font-size($font-size-base + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,5 +19,5 @@ label svg {
|
||||
|
||||
a .icon,
|
||||
.btn .icon {
|
||||
margin-right: floor($spacing-base / 3);
|
||||
margin-right: ($spacing-base / 2);
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ button.faux-link {
|
||||
a {
|
||||
border-bottom: 1px dotted currentColor;
|
||||
color: $color-link;
|
||||
font-weight: $font-weight-bold;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
|
@ -2,16 +2,10 @@
|
||||
// Examples
|
||||
// ==========================================================================
|
||||
|
||||
// For non supported browsers
|
||||
video {
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// Example players
|
||||
.plyr {
|
||||
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;
|
||||
|
||||
&.plyr--audio {
|
||||
@ -34,17 +28,9 @@ video {
|
||||
|
||||
// Style full supported player
|
||||
.plyr__cite {
|
||||
display: none;
|
||||
margin-top: $spacing-base;
|
||||
color: $color-gray-500;
|
||||
|
||||
.icon {
|
||||
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 {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
color: $gray;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
padding: ($spacing-base * 0.75);
|
||||
padding: $spacing-base;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
|
@ -11,3 +11,17 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@mixin font-size($size: 16) {
|
||||
@mixin font-size($size: $font-size-base) {
|
||||
font-size: $size * 1px; // Fallback in px
|
||||
font-size: calculate-rem($size);
|
||||
}
|
||||
|
@ -2,31 +2,41 @@
|
||||
// Colors
|
||||
// ==========================================================================
|
||||
|
||||
// Greyscale
|
||||
$gray-dark: #343f4a;
|
||||
$gray: #55646b;
|
||||
$gray-light: #cbd0d3;
|
||||
$gray-lighter: #dbe3e8;
|
||||
$off-white: #f2f5f7;
|
||||
// Grayscale
|
||||
$color-gray-900: hsl(210, 15%, 16%);
|
||||
$color-gray-800: lighten($color-gray-900, 9%);
|
||||
$color-gray-700: lighten($color-gray-800, 9%);
|
||||
$color-gray-600: lighten($color-gray-700, 9%);
|
||||
$color-gray-500: lighten($color-gray-600, 9%);
|
||||
$color-gray-400: lighten($color-gray-500, 9%);
|
||||
$color-gray-300: lighten($color-gray-400, 9%);
|
||||
$color-gray-200: lighten($color-gray-300, 9%);
|
||||
$color-gray-100: lighten($color-gray-200, 9%);
|
||||
$color-gray-50: lighten($color-gray-100, 9%);
|
||||
|
||||
// Branding
|
||||
$color-brand-primary: hsl(198, 100%, 50%);
|
||||
|
||||
// Text
|
||||
$color-text: #fff;
|
||||
|
||||
// Plyr
|
||||
$color-brand-primary: #1aafff;
|
||||
$color-text: $color-gray-700;
|
||||
$color-headings: $color-brand-primary;
|
||||
|
||||
// Brands
|
||||
$color-twitter: #4baaf4;
|
||||
$color-youtube: #cc181e;
|
||||
$color-vimeo: #19b7ed;
|
||||
|
||||
// Elements
|
||||
$color-link: #fff;
|
||||
$color-background: $color-brand-primary;
|
||||
$color-link: $color-brand-primary;
|
||||
|
||||
// Background
|
||||
$color-background-from: hsl(198, 100%, 94%);
|
||||
$color-background-to: hsl(198, 100%, 98%);
|
||||
|
||||
// Buttons
|
||||
$color-button-background: #fff;
|
||||
$color-button-text: $gray;
|
||||
$color-button-background: $color-brand-primary;
|
||||
$color-button-text: #fff;
|
||||
$color-button-background-hover: hsl(198, 100%, 55%);
|
||||
$color-button-count-background: #fff;
|
||||
$color-button-count-text: $color-gray-600;
|
||||
|
||||
// Focus
|
||||
$tab-focus-default-color: #fff;
|
||||
|
@ -9,4 +9,4 @@ $arrow-size: 5px;
|
||||
$border-radius-base: 4px;
|
||||
|
||||
// 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
|
||||
$plyr-font-smoothing: true;
|
||||
|
||||
// Colors
|
||||
$plyr-color-main: $color-brand-primary;
|
||||
|
||||
// Captions
|
||||
$plyr-font-size-captions-base: $plyr-font-size-base;
|
||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
||||
|
@ -2,4 +2,4 @@
|
||||
// Colors
|
||||
// ==========================================================================
|
||||
|
||||
$spacing-base: 20px;
|
||||
$spacing-base: 16px;
|
||||
|
@ -14,7 +14,6 @@ body {
|
||||
font-family: $font-sans-serif;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: $line-height-base;
|
||||
text-shadow: 0 1px 1px rgba(#000, 0.15);
|
||||
}
|
||||
|
||||
button,
|
||||
@ -26,7 +25,7 @@ textarea {
|
||||
|
||||
p,
|
||||
small {
|
||||
margin: 0 0 $spacing-base;
|
||||
margin: 0 0 ($spacing-base * 1.5);
|
||||
}
|
||||
|
||||
small {
|
||||
|
@ -4,8 +4,9 @@
|
||||
|
||||
h1 {
|
||||
@include font-size($font-size-h1);
|
||||
color: $color-headings;
|
||||
font-weight: $font-weight-bold;
|
||||
letter-spacing: $letter-spacing-headings;
|
||||
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
1643
dist/plyr.js
vendored
1643
dist/plyr.js
vendored
File diff suppressed because it is too large
Load Diff
4
dist/plyr.min.js
vendored
4
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
4
dist/plyr.min.mjs
vendored
4
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
1639
dist/plyr.mjs
vendored
1639
dist/plyr.mjs
vendored
File diff suppressed because it is too large
Load Diff
4087
dist/plyr.polyfilled.js
vendored
4087
dist/plyr.polyfilled.js
vendored
File diff suppressed because it is too large
Load Diff
4
dist/plyr.polyfilled.min.js
vendored
4
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
4
dist/plyr.polyfilled.min.mjs
vendored
4
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
4083
dist/plyr.polyfilled.mjs
vendored
4083
dist/plyr.polyfilled.mjs
vendored
File diff suppressed because it is too large
Load Diff
2
dist/plyr.svg
vendored
2
dist/plyr.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.6 KiB |
489
gulpfile.js
489
gulpfile.js
@ -1,493 +1,8 @@
|
||||
// ==========================================================================
|
||||
// Gulp build script
|
||||
// ==========================================================================
|
||||
/* global require, __dirname */
|
||||
/* eslint no-console: "off" */
|
||||
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
// ------------------------------------
|
||||
// JavaScript
|
||||
// ------------------------------------
|
||||
const terser = require('gulp-terser');
|
||||
const rollup = require('gulp-better-rollup');
|
||||
const babel = require('rollup-plugin-babel');
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
const resolve = require('rollup-plugin-node-resolve');
|
||||
// ------------------------------------
|
||||
// CSS
|
||||
// ------------------------------------
|
||||
const sass = require('gulp-sass');
|
||||
const clean = require('gulp-clean-css');
|
||||
const prefix = require('gulp-autoprefixer');
|
||||
const postcss = require('gulp-postcss');
|
||||
const customprops = require('postcss-custom-properties');
|
||||
// ------------------------------------
|
||||
// Images
|
||||
// ------------------------------------
|
||||
const svgstore = require('gulp-svgstore');
|
||||
const imagemin = require('gulp-imagemin');
|
||||
// ------------------------------------
|
||||
// Utils
|
||||
// ------------------------------------
|
||||
const del = require('del');
|
||||
const filter = require('gulp-filter');
|
||||
const header = require('gulp-header');
|
||||
const gitbranch = require('git-branch');
|
||||
const rename = require('gulp-rename');
|
||||
const replace = require('gulp-replace');
|
||||
const ansi = require('ansi-colors');
|
||||
const log = require('fancy-log');
|
||||
const open = require('gulp-open');
|
||||
const plumber = require('gulp-plumber');
|
||||
const size = require('gulp-size');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const through = require('through2');
|
||||
// ------------------------------------
|
||||
// Deployment
|
||||
// ------------------------------------
|
||||
const aws = require('aws-sdk');
|
||||
const publish = require('gulp-awspublish');
|
||||
const FastlyPurge = require('fastly-purge');
|
||||
// ------------------------------------
|
||||
// Configs
|
||||
// ------------------------------------
|
||||
const pkg = require('./package.json');
|
||||
const build = require('./build.json');
|
||||
const deploy = require('./deploy.json');
|
||||
// ------------------------------------
|
||||
// Info from package
|
||||
// ------------------------------------
|
||||
const { browserslist: browsers, version } = pkg;
|
||||
const minSuffix = '.min';
|
||||
const HubRegistry = require('gulp-hub');
|
||||
|
||||
// Get AWS config
|
||||
Object.values(deploy).forEach(target => {
|
||||
Object.assign(target, {
|
||||
publisher: publish.create({
|
||||
region: target.region,
|
||||
params: {
|
||||
Bucket: target.bucket,
|
||||
},
|
||||
credentials: new aws.SharedIniFileCredentials({ profile: 'plyr' }),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Paths
|
||||
const paths = {
|
||||
plyr: {
|
||||
// Source paths
|
||||
src: {
|
||||
sass: path.join(__dirname, 'src/sass/**/*.scss'),
|
||||
js: path.join(__dirname, 'src/js/**/*.js'),
|
||||
sprite: path.join(__dirname, 'src/sprite/*.svg'),
|
||||
},
|
||||
|
||||
// Output paths
|
||||
output: path.join(__dirname, 'dist/'),
|
||||
},
|
||||
demo: {
|
||||
// Source paths
|
||||
src: {
|
||||
sass: path.join(__dirname, 'demo/src/sass/**/*.scss'),
|
||||
js: path.join(__dirname, 'demo/src/js/**/*.js'),
|
||||
},
|
||||
|
||||
// Output paths
|
||||
output: path.join(__dirname, 'demo/dist/'),
|
||||
|
||||
// Demo
|
||||
root: path.join(__dirname, 'demo/'),
|
||||
},
|
||||
upload: [
|
||||
path.join(__dirname, `dist/*${minSuffix}.*`),
|
||||
path.join(__dirname, 'dist/*.css'),
|
||||
path.join(__dirname, 'dist/*.svg'),
|
||||
path.join(__dirname, `demo/dist/*${minSuffix}.*`),
|
||||
path.join(__dirname, 'demo/dist/*.css'),
|
||||
path.join(__dirname, 'demo/dist/*.svg'),
|
||||
],
|
||||
};
|
||||
|
||||
// Task arrays
|
||||
const tasks = {
|
||||
css: [],
|
||||
js: [],
|
||||
sprite: [],
|
||||
clean: 'clean',
|
||||
};
|
||||
|
||||
// Size plugin
|
||||
const sizeOptions = { showFiles: true, gzip: true };
|
||||
|
||||
// Clean out /dist
|
||||
gulp.task(tasks.clean, done => {
|
||||
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
||||
|
||||
// Don't delete the mp4
|
||||
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
|
||||
|
||||
del(dirs);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
// JavaScript
|
||||
Object.entries(build.js).forEach(([filename, entry]) => {
|
||||
entry.formats.forEach(format => {
|
||||
const name = `js:${filename}:${format}`;
|
||||
tasks.js.push(name);
|
||||
const polyfill = filename.includes('polyfilled');
|
||||
const extension = format === 'es' ? 'mjs' : 'js';
|
||||
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(entry.src)
|
||||
.pipe(plumber())
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(
|
||||
rollup(
|
||||
{
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs(),
|
||||
babel({
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
{
|
||||
// debug: true,
|
||||
useBuiltIns: polyfill ? 'usage' : false,
|
||||
corejs: polyfill ? 3 : undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
babelrc: false,
|
||||
exclude: [/\/core-js\//],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: entry.namespace,
|
||||
format,
|
||||
},
|
||||
),
|
||||
)
|
||||
.pipe(header('typeof navigator === "object" && ')) // "Support" SSR (#935)
|
||||
.pipe(
|
||||
rename({
|
||||
extname: `.${extension}`,
|
||||
}),
|
||||
)
|
||||
.pipe(gulp.dest(entry.dist))
|
||||
.pipe(filter(`**/*.${extension}`))
|
||||
.pipe(terser())
|
||||
.pipe(rename({ suffix: minSuffix }))
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(sourcemaps.write(''))
|
||||
.pipe(gulp.dest(entry.dist)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// CSS
|
||||
Object.entries(build.css).forEach(([filename, entry]) => {
|
||||
const name = `css:${filename}`;
|
||||
tasks.css.push(name);
|
||||
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(entry.src)
|
||||
.pipe(plumber())
|
||||
.pipe(sass())
|
||||
.pipe(
|
||||
prefix(browsers, {
|
||||
cascade: false,
|
||||
}),
|
||||
)
|
||||
.pipe(postcss([customprops()]))
|
||||
.pipe(clean())
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(gulp.dest(entry.dist)),
|
||||
);
|
||||
});
|
||||
|
||||
// SVG Sprites
|
||||
Object.entries(build.sprite).forEach(([filename, entry]) => {
|
||||
const name = `sprite:${filename}`;
|
||||
tasks.sprite.push(name);
|
||||
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(entry.src)
|
||||
.pipe(plumber())
|
||||
.pipe(imagemin())
|
||||
.pipe(svgstore())
|
||||
.pipe(rename({ basename: path.parse(filename).name }))
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(gulp.dest(entry.dist)),
|
||||
);
|
||||
});
|
||||
|
||||
// Build all JS
|
||||
gulp.task('js', () => gulp.parallel(...tasks.js));
|
||||
|
||||
// Watch for file changes
|
||||
gulp.task('watch', () => {
|
||||
// Plyr core
|
||||
gulp.watch(paths.plyr.src.js, gulp.parallel(...tasks.js));
|
||||
gulp.watch(paths.plyr.src.sass, gulp.parallel(...tasks.css));
|
||||
gulp.watch(paths.plyr.src.sprite, gulp.parallel(...tasks.sprite));
|
||||
|
||||
// Demo
|
||||
gulp.watch(paths.demo.src.js, gulp.parallel(...tasks.js));
|
||||
gulp.watch(paths.demo.src.sass, gulp.parallel(...tasks.css));
|
||||
});
|
||||
|
||||
// Build distribution
|
||||
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite)));
|
||||
|
||||
// Default gulp task
|
||||
gulp.task('default', gulp.series('build', 'watch'));
|
||||
|
||||
// Publish a version to CDN and demo
|
||||
// --------------------------------------------
|
||||
// Get deployment config
|
||||
let credentials = {};
|
||||
try {
|
||||
credentials = require('./credentials.json'); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// Get branch info
|
||||
const branch = {
|
||||
current: gitbranch.sync(),
|
||||
master: 'master',
|
||||
beta: 'beta',
|
||||
};
|
||||
|
||||
const maxAge = 31536000; // 1 year
|
||||
const options = {
|
||||
cdn: {
|
||||
headers: {
|
||||
'Cache-Control': `max-age=${maxAge}`,
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
uploadPath: branch.current === branch.beta ? 'beta' : null,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||
},
|
||||
},
|
||||
symlinks(ver, filename) {
|
||||
return {
|
||||
headers: {
|
||||
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
||||
'x-amz-website-redirect-location': `/${ver}/${filename}`,
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const regex =
|
||||
'(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
|
||||
const semver = new RegExp(`v${regex}`, 'gi');
|
||||
const localPath = new RegExp('(../)?dist', 'gi');
|
||||
const versionPath = `https://${deploy.cdn.domain}/${version}`;
|
||||
const cdnpath = new RegExp(`${deploy.cdn.domain}/${regex}/`, 'gi');
|
||||
|
||||
const renameFile = rename(p => {
|
||||
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||
});
|
||||
|
||||
// Check we're on the correct branch to deploy
|
||||
const canDeploy = () => {
|
||||
const allowed = [branch.master, branch.beta];
|
||||
|
||||
if (!allowed.includes(branch.current)) {
|
||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gulp.task('version', done => {
|
||||
if (!canDeploy()) {
|
||||
done();
|
||||
return null;
|
||||
}
|
||||
|
||||
const { domain } = deploy.cdn;
|
||||
|
||||
log(`Uploading ${ansi.green.bold(version)} to ${ansi.cyan(domain)}...`);
|
||||
|
||||
// Replace versioned URLs in source
|
||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
||||
|
||||
return gulp
|
||||
.src(files.map(file => path.join(__dirname, `src/js/${file}`)), { base: '.' })
|
||||
.pipe(replace(semver, `v${version}`))
|
||||
.pipe(replace(cdnpath, `${domain}/${version}/`))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
// Publish version to CDN bucket
|
||||
gulp.task('cdn', done => {
|
||||
if (!canDeploy()) {
|
||||
done();
|
||||
return null;
|
||||
}
|
||||
|
||||
const { domain, publisher } = deploy.cdn;
|
||||
|
||||
if (!publisher) {
|
||||
throw new Error('No publisher instance. Check AWS configuration.');
|
||||
}
|
||||
|
||||
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
||||
|
||||
// Upload to CDN
|
||||
return (
|
||||
gulp
|
||||
.src(paths.upload)
|
||||
.pipe(renameFile)
|
||||
// Remove min suffix from source map URL
|
||||
.pipe(
|
||||
replace(
|
||||
/sourceMappingURL=([\w-?.]+)/,
|
||||
(match, filename) => `sourceMappingURL=${filename.replace(minSuffix, '')}`,
|
||||
),
|
||||
)
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(publisher.publish(options.cdn.headers))
|
||||
.pipe(publish.reporter())
|
||||
);
|
||||
});
|
||||
|
||||
// Purge the fastly cache incase any 403/404 are cached
|
||||
gulp.task('purge', () => {
|
||||
if (!Object.keys(credentials).includes('fastly')) {
|
||||
throw new Error('Fastly credentials required to purge cache.');
|
||||
}
|
||||
|
||||
const { fastly } = credentials;
|
||||
const list = [];
|
||||
|
||||
return gulp
|
||||
.src(paths.upload)
|
||||
.pipe(
|
||||
through.obj((file, enc, cb) => {
|
||||
const filename = file.path.split('/').pop();
|
||||
list.push(`${versionPath}/${filename.replace(minSuffix, '')}`);
|
||||
cb(null);
|
||||
}),
|
||||
)
|
||||
.on('end', () => {
|
||||
const purge = new FastlyPurge(fastly.token);
|
||||
|
||||
list.forEach(url => {
|
||||
log(`Purging ${ansi.cyan(url)}...`);
|
||||
|
||||
purge.url(url, (error, result) => {
|
||||
if (error) {
|
||||
log.error(error);
|
||||
} else if (result) {
|
||||
log(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Publish to demo bucket
|
||||
gulp.task('demo', done => {
|
||||
if (!canDeploy()) {
|
||||
done();
|
||||
return null;
|
||||
}
|
||||
|
||||
const { publisher } = deploy.demo;
|
||||
const { domain } = deploy.cdn;
|
||||
|
||||
if (!publisher) {
|
||||
throw new Error('No publisher instance. Check AWS configuration.');
|
||||
}
|
||||
|
||||
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
||||
|
||||
// Replace versioned files in readme.md
|
||||
gulp.src([`${__dirname}/readme.md`])
|
||||
.pipe(replace(cdnpath, `${domain}/${version}/`))
|
||||
.pipe(gulp.dest(__dirname));
|
||||
|
||||
// Replace local file paths with remote paths in demo HTML
|
||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||
const index = `${paths.demo.root}index.html`;
|
||||
const error = `${paths.demo.root}error.html`;
|
||||
const pages = [index];
|
||||
|
||||
if (branch.current === branch.master) {
|
||||
pages.push(error);
|
||||
}
|
||||
|
||||
return gulp
|
||||
.src(pages)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(publisher.publish(options.demo.headers))
|
||||
.pipe(publish.reporter());
|
||||
});
|
||||
|
||||
gulp.task('error', done => {
|
||||
// Only update CDN for master (prod)
|
||||
if (!canDeploy() || branch.current !== branch.master) {
|
||||
done();
|
||||
return null;
|
||||
}
|
||||
|
||||
const { publisher } = deploy.cdn;
|
||||
|
||||
if (!publisher) {
|
||||
throw new Error('No publisher instance. Check AWS configuration.');
|
||||
}
|
||||
|
||||
// Replace local file paths with remote paths in demo HTML
|
||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||
// Upload error.html to cdn
|
||||
return gulp
|
||||
.src(`${paths.demo.root}error.html`)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(publisher.publish(options.demo.headers))
|
||||
.pipe(publish.reporter());
|
||||
});
|
||||
|
||||
// Open the demo site to check it's ok
|
||||
gulp.task('open', () => {
|
||||
const { domain } = deploy.demo;
|
||||
|
||||
return gulp.src(__filename).pipe(
|
||||
open({
|
||||
uri: `https://${domain}/${branch.current === branch.beta ? 'beta' : ''}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Do everything
|
||||
gulp.task(
|
||||
'deploy',
|
||||
gulp.series(
|
||||
'version',
|
||||
tasks.clean,
|
||||
gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite),
|
||||
'cdn',
|
||||
'demo',
|
||||
'purge',
|
||||
'open',
|
||||
),
|
||||
);
|
||||
gulp.registry(new HubRegistry(['tasks/*.js']));
|
||||
|
79
package.json
79
package.json
@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.10",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "https://plyr.io",
|
||||
"author": "Sam Potts <sam@potts.es>",
|
||||
"main": "dist/plyr.js",
|
||||
"types": "src/js/plyr.d.ts",
|
||||
"module": "dist/plyr.min.mjs",
|
||||
"jsnext:main": "dist/plyr.min.mjs",
|
||||
"browser": "dist/plyr.min.js",
|
||||
@ -33,65 +34,67 @@
|
||||
"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'",
|
||||
"deploy": "yarn lint && gulp deploy"
|
||||
"deploy": "yarn lint && gulp version && gulp build && gulp deploy",
|
||||
"prettier": "prettier --write ./src/js/*.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ansi-colors": "^3.2.4",
|
||||
"aws-sdk": "^2.466.0",
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"del": "^4.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-config-prettier": "^4.3.0",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"eslint-plugin-simple-import-sort": "^3.1.1",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"autoprefixer": "^9.7.5",
|
||||
"aws-sdk": "^2.648.0",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"browser-sync": "^2.26.7",
|
||||
"del": "^5.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-base": "^14.1.0",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^5.0.2",
|
||||
"fancy-log": "^1.3.3",
|
||||
"fastly-purge": "^1.0.1",
|
||||
"git-branch": "^2.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^6.1.0",
|
||||
"gulp-awspublish": "^4.0.0",
|
||||
"gulp-awspublish": "^4.1.1",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-clean-css": "^4.2.0",
|
||||
"gulp-filter": "^6.0.0",
|
||||
"gulp-header": "^2.0.7",
|
||||
"gulp-imagemin": "^6.0.0",
|
||||
"gulp-header": "^2.0.9",
|
||||
"gulp-hub": "^4.2.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-open": "^3.0.1",
|
||||
"gulp-plumber": "^1.2.1",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-replace": "^1.0.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-svgstore": "^7.0.1",
|
||||
"gulp-terser": "^1.2.0",
|
||||
"postcss-custom-properties": "^8.0.10",
|
||||
"prettier-eslint": "^8.8.2",
|
||||
"postcss-clean": "^1.1.0",
|
||||
"postcss-custom-properties": "^9.1.1",
|
||||
"prettier-eslint": "^9.0.1",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"remark-cli": "^6.0.1",
|
||||
"remark-validate-links": "^8.0.2",
|
||||
"rollup": "^1.13.0",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.1",
|
||||
"stylelint": "^10.0.1",
|
||||
"stylelint-config-prettier": "^5.2.0",
|
||||
"stylelint-config-recommended": "^2.2.0",
|
||||
"stylelint-config-sass-guidelines": "^6.0.0",
|
||||
"stylelint-order": "^3.0.0",
|
||||
"stylelint-scss": "^3.8.0",
|
||||
"remark-cli": "^7.0.1",
|
||||
"remark-validate-links": "^10.0.0",
|
||||
"rollup": "^2.2.0",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"stylelint": "^13.2.1",
|
||||
"stylelint-config-prettier": "^8.0.1",
|
||||
"stylelint-config-recommended": "^3.0.0",
|
||||
"stylelint-config-sass-guidelines": "^7.0.0",
|
||||
"stylelint-order": "^4.0.0",
|
||||
"stylelint-scss": "^3.16.0",
|
||||
"stylelint-selector-bem-pattern": "^2.1.0",
|
||||
"through2": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.1.3",
|
||||
"core-js": "^3.6.4",
|
||||
"custom-event-polyfill": "^1.0.7",
|
||||
"loadjs": "^3.6.1",
|
||||
"rangetouch": "^2.0.0",
|
||||
"raven-js": "^3.27.1",
|
||||
"url-polyfill": "^1.1.5"
|
||||
"loadjs": "^4.2.0",
|
||||
"rangetouch": "^2.0.1",
|
||||
"url-polyfill": "^1.1.8"
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,29 @@
|
||||
"**/node_modules": true,
|
||||
"**/dist": true
|
||||
},
|
||||
|
||||
// Linting
|
||||
"stylelint.enable": true,
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"javascript.validate.enable": false,
|
||||
|
||||
// Prettier
|
||||
"prettier.eslintIntegration": true,
|
||||
"prettier.stylelintIntegration": true,
|
||||
|
||||
// Formatting
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
// Trim on save
|
||||
"files.trimTrailingWhitespace": true
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
// Special file associations
|
||||
"files.associations": {
|
||||
".eslintrc": "jsonc"
|
||||
},
|
||||
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
212
readme.md
212
readme.md
@ -1,18 +1,19 @@
|
||||
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://gitpod.io/#https://github.com/sampotts/plyr) [](https://opencollective.com/plyr)
|
||||
|
||||
[](https://plyr.io)
|
||||
|
||||
# Features
|
||||
|
||||
- 📼 **HTML Video & Audio, YouTube & Vimeo** - support for the major formats
|
||||
- 💪 **Accessible** - full support for VTT captions and screen readers
|
||||
- 🔧 **[Customisable](#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
|
||||
- 🔧 **[Customizable](#html)** - make the player look how you want with the markup you want
|
||||
- 😎 **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
|
||||
- 📱 **Responsive** - works with any screen size
|
||||
- 📼 **HTML Video & Audio** - support for both formats
|
||||
- 📺 **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||
- 💵 **[Monetization](#ads)** - make money from your videos
|
||||
- 📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
|
||||
- 🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||
@ -25,7 +26,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
|
||||
- 📖 **Multiple captions** - support for multiple caption tracks
|
||||
- 🌎 **i18n support** - support for internationalization of controls
|
||||
- 👌 **[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
|
||||
|
||||
### Demos
|
||||
@ -109,7 +110,15 @@ Or the `<div>` non progressively enhanced method:
|
||||
|
||||
## 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');
|
||||
```
|
||||
|
||||
Alternatively 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
|
||||
<script src="path/to/plyr.js"></script>
|
||||
@ -123,18 +132,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.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.5.4/plyr.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.5.10/plyr.js"></script>
|
||||
```
|
||||
|
||||
...or...
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.5.4/plyr.polyfilled.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.5.10/plyr.polyfilled.js"></script>
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
Include the `plyr.css` stylsheet into your `<head>`
|
||||
Include the `plyr.css` stylsheet into your `<head>`.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="path/to/plyr.css" />
|
||||
@ -143,13 +152,13 @@ Include the `plyr.css` stylsheet into your `<head>`
|
||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.4/plyr.css" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.10/plyr.css" />
|
||||
```
|
||||
|
||||
## SVG Sprite
|
||||
|
||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.4/plyr.svg`.
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.10/plyr.svg`.
|
||||
|
||||
# Ads
|
||||
|
||||
@ -161,12 +170,40 @@ Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?a
|
||||
|
||||
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||
|
||||
If you do not wish to use Vi, you can set your own `ads.tagUrl` [option](#options).
|
||||
|
||||
# Advanced
|
||||
|
||||
## SASS
|
||||
## Custom CSS
|
||||
|
||||
You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to
|
||||
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should be already!) as all declarations use the W3C definitions.
|
||||
If you want to change any design tokens used for the rendering of the player, you can do so using [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
|
||||
|
||||
Here's a list of the properties and what they are used for:
|
||||
|
||||
| Name | Description | Fallback |
|
||||
| ------------------- | ----------------------------------------------- | --------------------- |
|
||||
| `--plyr-color-main` | The main branding color - used for highlighting | `hsl(198, 100%, 50%)` |
|
||||
|
||||
You can set them in your CSS:
|
||||
|
||||
```css
|
||||
.player {
|
||||
--plyr-color-main: #1ac266;
|
||||
}
|
||||
```
|
||||
|
||||
...or in your HTML:
|
||||
|
||||
```html
|
||||
<video class="player" style="--plyr-color-main: #1ac266;">
|
||||
...
|
||||
</vieo>
|
||||
```
|
||||
|
||||
### SASS
|
||||
|
||||
You can use `plyr.scss` file included in `/src/sass` as part of your build and change variables to suit your design. The SASS requires you to
|
||||
use [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) (you should be already!) as all declarations use the W3C definitions.
|
||||
|
||||
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
||||
you write. Check out the JavaScript source for more on this.
|
||||
@ -204,7 +241,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:
|
||||
|
||||
- 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 [jQuery](https://jquery.com) object
|
||||
|
||||
@ -212,7 +249,7 @@ _Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element
|
||||
|
||||
#### 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
|
||||
const player = new Plyr('#player');
|
||||
@ -238,7 +275,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));
|
||||
```
|
||||
|
||||
...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
|
||||
const players = Plyr.setup('.js-player');
|
||||
@ -269,7 +306,7 @@ 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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
@ -295,10 +332,10 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `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 | `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. |
|
||||
| `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`: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically. |
|
||||
| `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. |
|
||||
| `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: '', tagUrl: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. `tagUrl` is a URL for a custom VAST tag if you're not using Vi. |
|
||||
| `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. |
|
||||
| `vimeo` | Object | `{ byline: false, portrait: false, title: false, speed: true, transparent: false }` | See [Vimeo embed options](https://github.com/vimeo/player.js/#embed-options). Some are set automatically based on other config options, namely: `loop`, `autoplay`, `muted`, `gesture`, `playsinline` |
|
||||
| `youtube` | Object | `{ noCookie: false, rel: 0, showinfo: 0, iv_load_policy: 3, modestbranding: 1 }` | See [YouTube embed options](https://developers.google.com/youtube/player_parameters#Parameters). The only custom option is `noCookie` to use an alternative to YouTube that doesn't use cookies (useful for GDPR, etc). Some are set automatically based on other config options, namely: `autoplay`, `hl`, `controls`, `disablekb`, `playsinline`, `cc_load_policy`, `cc_lang_pref`, `widget_referrer` |
|
||||
@ -342,30 +379,30 @@ player.play(); // Start playback
|
||||
player.fullscreen.enter(); // Enter fullscreen
|
||||
```
|
||||
|
||||
| Method | Parameters | Description |
|
||||
| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `play()`¹ | - | Start playback. |
|
||||
| `pause()` | - | Pause playback. |
|
||||
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
||||
| `stop()` | - | Stop playback and reset to start. |
|
||||
| `restart()` | - | Restart playback. |
|
||||
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
||||
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
|
||||
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||
| `supports(type)` | String | Check support for a mime type. |
|
||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||
| Method | Parameters | Description |
|
||||
| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `play()`¹ | - | Start playback. |
|
||||
| `pause()` | - | Pause playback. |
|
||||
| `togglePlay(toggle)`¹ | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
||||
| `stop()` | - | Stop playback and reset to start. |
|
||||
| `restart()` | - | Restart playback. |
|
||||
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
||||
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
|
||||
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||
| `supports(type)` | String | Check support for a mime type. |
|
||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||
|
||||
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
||||
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for most browsers - e.g. Chrome, Firefox, Opera, Safari and Edge [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
||||
|
||||
## Getters and Setters
|
||||
|
||||
@ -438,6 +475,9 @@ player.source = {
|
||||
},
|
||||
],
|
||||
poster: '/path/to/poster.jpg',
|
||||
previewThumbnails: {
|
||||
src: '/path/to/thumbnails.vtt',
|
||||
},
|
||||
tracks: [
|
||||
{
|
||||
kind: 'captions',
|
||||
@ -507,13 +547,14 @@ player.source = {
|
||||
|
||||
_Note:_ `src` property for YouTube and Vimeo can either be the video ID or the whole URL.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type` | String | Either `video` or `audio`. _Note:_ YouTube and Vimeo are currently not supported as audio sources. |
|
||||
| `title` | String | _Optional._ Title of the new media. Used for the `aria-label` attribute on the play button, and outer container. YouTube and Vimeo are populated automatically. |
|
||||
| `sources` | Array | This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required. |
|
||||
| `poster`¹ | String | The URL for the poster image (HTML5 video only). |
|
||||
| `tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes. |
|
||||
| Property | Type | Description |
|
||||
| ------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type` | String | Either `video` or `audio`. _Note:_ YouTube and Vimeo are currently not supported as audio sources. |
|
||||
| `title` | String | _Optional._ Title of the new media. Used for the `aria-label` attribute on the play button, and outer container. YouTube and Vimeo are populated automatically. |
|
||||
| `sources` | Array | This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required. |
|
||||
| `poster`¹ | String | The URL for the poster image (HTML5 video only). |
|
||||
| `tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes. |
|
||||
| `previewThumbnails`¹ | Object | The same object like in the `previewThumbnails` constructor option. This means you can either change the thumbnails vtt via the `src` key or disable the thumbnails plugin for the next video by passing `{ enabled: false }`. |
|
||||
|
||||
1. HTML5 only
|
||||
|
||||
@ -622,16 +663,16 @@ Fullscreen in Plyr is supported by all browsers that [currently support it](http
|
||||
|
||||
Plyr supports the last 2 versions of most _modern_ browsers.
|
||||
|
||||
| Browser | Supported |
|
||||
| ------------- | ------------- |
|
||||
| Safari | ✓ |
|
||||
| Mobile Safari | ✓¹ |
|
||||
| Firefox | ✓ |
|
||||
| Chrome | ✓ |
|
||||
| Opera | ✓ |
|
||||
| Edge | ✓ |
|
||||
| IE11 | ✓³ |
|
||||
| IE10 | ✓²³ |
|
||||
| Browser | Supported |
|
||||
| ------------- | --------------- |
|
||||
| Safari | ✓ |
|
||||
| Mobile Safari | ✓¹ |
|
||||
| Firefox | ✓ |
|
||||
| Chrome | ✓ |
|
||||
| Opera | ✓ |
|
||||
| Edge | ✓ |
|
||||
| IE11 | ✓³ |
|
||||
| IE10 | ✓<sup>2,3</sup> |
|
||||
|
||||
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled as they are handled device wide.
|
||||
2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported. No native fullscreen support, fallback can be used (see [options](#options)).
|
||||
@ -655,7 +696,7 @@ The arguments are:
|
||||
- Provider (`html5`, `youtube` or `vimeo`)
|
||||
- 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:
|
||||
|
||||
@ -671,14 +712,15 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
|
||||
|
||||
Some awesome folks have made plugins for CMSs and Components for JavaScript frameworks:
|
||||
|
||||
| Type | Maintainer | Link |
|
||||
| --------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
|
||||
| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
|
||||
| React | Jose Miguel Bejarano ([@xDae](https://github.com/xDae)) | [https://github.com/xDae/react-plyr](https://github.com/xDae/react-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) |
|
||||
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
||||
| Type | Maintainer | Link |
|
||||
| --------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| WordPress | Brandon Lavigne ([@drrobotnik](https://github.com/drrobotnik)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
|
||||
| Angular | Simon Bobrov ([@smnbbrv](https://github.com/smnbbrv)) | [https://github.com/smnbbrv/ngx-plyr](https://github.com/smnbbrv/ngx-plyr) |
|
||||
| React | Chintan Prajapati ([@chintan9](https://github.com/chintan9)) | [https://github.com/chintan9/plyr-react](https://github.com/chintan9/plyr-react) |
|
||||
| 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) |
|
||||
| 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
|
||||
|
||||
@ -703,6 +745,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)
|
||||
- [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/)
|
||||
- [Front End Focus #177](https://frontendfoc.us/issues/177)
|
||||
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||
- [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)
|
||||
@ -722,13 +765,11 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
|
||||
- [Sparkk TV](https://www.sparkktv.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
|
||||
|
||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||
|
||||
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||
- [PayPal's Accessible HTML5 Video Player (which Plyr was originally ported from)](https://github.com/paypal/accessible-html5-video-player)
|
||||
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||
|
||||
# Thanks
|
||||
@ -741,6 +782,29 @@ Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN servic
|
||||
|
||||
Massive thanks to [Sentry](https://sentry.io/) for providing the logging services for the demo site.
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
|
||||
<a href="https://github.com/sampotts/plyr/graphs/contributors"><img src="https://opencollective.com/plyr/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/plyr/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/plyr"><img src="https://opencollective.com/plyr/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/plyr/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/plyr/organization/0/website"><img src="https://opencollective.com/plyr/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/plyr/organization/1/website"><img src="https://opencollective.com/plyr/organization/1/avatar.svg"></a><a href="https://opencollective.com/plyr/organization/2/website"><img src="https://opencollective.com/plyr/organization/2/avatar.svg"></a>
|
||||
|
||||
# Copyright and License
|
||||
|
||||
[The MIT license](license.md)
|
||||
|
@ -133,7 +133,8 @@ const captions = {
|
||||
});
|
||||
|
||||
// Turn off native caption rendering to avoid double captions
|
||||
Object.assign(track, { mode: 'hidden' });
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
track.mode = 'hidden';
|
||||
|
||||
// Add event listener for cue changes
|
||||
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
|
||||
@ -150,7 +151,11 @@ const captions = {
|
||||
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
||||
|
||||
// Update available languages in list
|
||||
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
|
||||
if (
|
||||
is.array(this.config.controls) &&
|
||||
this.config.controls.includes('settings') &&
|
||||
this.config.settings.includes('captions')
|
||||
) {
|
||||
controls.setCaptionsMenu.call(this);
|
||||
}
|
||||
},
|
||||
|
@ -61,7 +61,7 @@ const defaults = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.2/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
||||
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
@ -69,7 +69,10 @@ const defaults = {
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 576,
|
||||
// The options to display in the UI, if available for the source media
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||
forced: false,
|
||||
onChange: null,
|
||||
},
|
||||
|
||||
// Set loops
|
||||
@ -82,7 +85,8 @@ const defaults = {
|
||||
// Speed default and options to display
|
||||
speed: {
|
||||
selected: 1,
|
||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
||||
},
|
||||
|
||||
// Keyboard shortcut settings
|
||||
@ -128,6 +132,7 @@ const defaults = {
|
||||
// 'fast-forward',
|
||||
'progress',
|
||||
'current-time',
|
||||
// 'duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'captions',
|
||||
@ -163,6 +168,7 @@ const defaults = {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
pip: 'PIP',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
@ -195,7 +201,7 @@ const defaults = {
|
||||
},
|
||||
youtube: {
|
||||
sdk: 'https://www.youtube.com/iframe_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),fileDetails)&part=snippet',
|
||||
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
||||
},
|
||||
googleIMA: {
|
||||
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||
@ -413,6 +419,11 @@ const defaults = {
|
||||
title: false,
|
||||
speed: true,
|
||||
transparent: false,
|
||||
// These settings require a pro or premium account to work
|
||||
sidedock: false,
|
||||
controls: false,
|
||||
// Custom settings from Plyr
|
||||
referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||
},
|
||||
|
||||
// YouTube plugin
|
||||
|
43
src/js/controls.js
vendored
43
src/js/controls.js
vendored
@ -28,7 +28,7 @@ import {
|
||||
import { off, on } from './utils/events';
|
||||
import i18n from './utils/i18n';
|
||||
import is from './utils/is';
|
||||
import loadSprite from './utils/loadSprite';
|
||||
import loadSprite from './utils/load-sprite';
|
||||
import { extend } from './utils/objects';
|
||||
import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
|
||||
import { formatTime, getHours } from './utils/time';
|
||||
@ -111,7 +111,7 @@ const controls = {
|
||||
setAttributes(
|
||||
icon,
|
||||
extend(attributes, {
|
||||
role: 'presentation',
|
||||
'aria-hidden': 'true',
|
||||
focusable: 'false',
|
||||
}),
|
||||
);
|
||||
@ -139,10 +139,7 @@ const controls = {
|
||||
// Create hidden text label
|
||||
createLabel(key, attr = {}) {
|
||||
const text = i18n.get(key, this.config);
|
||||
|
||||
const attributes = Object.assign({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '),
|
||||
});
|
||||
const attributes = { ...attr, class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') };
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
@ -402,7 +399,8 @@ const controls = {
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
|
||||
bindMenuItemShortcuts(menuItem, type) {
|
||||
// Navigate through menus via arrow keys and space
|
||||
on(
|
||||
on.call(
|
||||
this,
|
||||
menuItem,
|
||||
'keydown keyup',
|
||||
event => {
|
||||
@ -452,7 +450,7 @@ const controls = {
|
||||
|
||||
// Enter will fire a `click` event but we still need to manage focus
|
||||
// So we bind to keyup which fires after and set focus here
|
||||
on(menuItem, 'keyup', event => {
|
||||
on.call(this, menuItem, 'keyup', event => {
|
||||
if (event.which !== 13) {
|
||||
return;
|
||||
}
|
||||
@ -1046,7 +1044,7 @@ const controls = {
|
||||
},
|
||||
|
||||
// Set a list of available captions languages
|
||||
setSpeedMenu(options) {
|
||||
setSpeedMenu() {
|
||||
// Menu required
|
||||
if (!is.element(this.elements.settings.panels.speed)) {
|
||||
return;
|
||||
@ -1055,15 +1053,8 @@ const controls = {
|
||||
const type = 'speed';
|
||||
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
|
||||
|
||||
// Set the speed options
|
||||
if (is.array(options)) {
|
||||
this.options.speed = options;
|
||||
} else if (this.isHTML5 || this.isVimeo) {
|
||||
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
||||
}
|
||||
|
||||
// Set options if passed and filter based on config
|
||||
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
|
||||
// Filter out invalid speeds
|
||||
this.options.speed = this.options.speed.filter(o => o >= this.minimumSpeed && o <= this.maximumSpeed);
|
||||
|
||||
// Toggle the pane and tab
|
||||
const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
|
||||
@ -1380,7 +1371,9 @@ const controls = {
|
||||
}
|
||||
|
||||
// Volume range control
|
||||
if (control === 'volume') {
|
||||
// Ignored on iOS as it's handled globally
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
if (control === 'volume' && !browser.isIos) {
|
||||
// Set the attributes
|
||||
const attributes = {
|
||||
max: 1,
|
||||
@ -1463,7 +1456,7 @@ const controls = {
|
||||
bindMenuItemShortcuts.call(this, menuItem, type);
|
||||
|
||||
// Show menu on click
|
||||
on(menuItem, 'click', () => {
|
||||
on.call(this, menuItem, 'click', () => {
|
||||
showMenuPanel.call(this, type, false);
|
||||
});
|
||||
|
||||
@ -1515,7 +1508,8 @@ const controls = {
|
||||
);
|
||||
|
||||
// Go back via keyboard
|
||||
on(
|
||||
on.call(
|
||||
this,
|
||||
pane,
|
||||
'keydown',
|
||||
event => {
|
||||
@ -1535,7 +1529,7 @@ const controls = {
|
||||
);
|
||||
|
||||
// Go back via button click
|
||||
on(backButton, 'click', () => {
|
||||
on.call(this, backButton, 'click', () => {
|
||||
showMenuPanel.call(this, 'home', false);
|
||||
});
|
||||
|
||||
@ -1581,6 +1575,11 @@ const controls = {
|
||||
target: '_blank',
|
||||
});
|
||||
|
||||
// Set download attribute for HTML5 only
|
||||
if (this.isHTML5) {
|
||||
attributes.download = '';
|
||||
}
|
||||
|
||||
const { download } = this.config.urls;
|
||||
|
||||
if (!is.url(download) && this.isEmbed) {
|
||||
|
@ -4,83 +4,11 @@
|
||||
// https://webkit.org/blog/7929/designing-websites-for-iphone-x/
|
||||
// ==========================================================================
|
||||
|
||||
import { repaint } from './utils/animation';
|
||||
import browser from './utils/browser';
|
||||
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
||||
import { getElements, hasClass, toggleClass } from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
|
||||
function onChange() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update toggle button
|
||||
const button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// Trigger an event
|
||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||
|
||||
// Trap focus in container
|
||||
if (!browser.isIos) {
|
||||
trapFocus.call(this.player, this.target, this.active);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFallback(toggle = false) {
|
||||
// Store or restore scroll position
|
||||
if (toggle) {
|
||||
this.scrollPosition = {
|
||||
x: window.scrollX || 0,
|
||||
y: window.scrollY || 0,
|
||||
};
|
||||
} else {
|
||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||
}
|
||||
|
||||
// Toggle scroll
|
||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||
|
||||
// Toggle class hook
|
||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||
|
||||
// Force full viewport on iPhone X+
|
||||
if (browser.isIos) {
|
||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||
const property = 'viewport-fit=cover';
|
||||
|
||||
// Inject the viewport meta if required
|
||||
if (!viewport) {
|
||||
viewport = document.createElement('meta');
|
||||
viewport.setAttribute('name', 'viewport');
|
||||
}
|
||||
|
||||
// Check if the property already exists
|
||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||
|
||||
if (toggle) {
|
||||
this.cleanupViewport = !hasProperty;
|
||||
|
||||
if (!hasProperty) {
|
||||
viewport.content += `,${property}`;
|
||||
}
|
||||
} else if (this.cleanupViewport) {
|
||||
viewport.content = viewport.content
|
||||
.split(',')
|
||||
.filter(part => part.trim() !== property)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
// Force a repaint as sometimes Safari doesn't want to fill the screen
|
||||
setTimeout(() => repaint(this.target), 100);
|
||||
}
|
||||
|
||||
// Toggle button and fire events
|
||||
onChange.call(this);
|
||||
}
|
||||
import { silencePromise } from './utils/promise';
|
||||
|
||||
class Fullscreen {
|
||||
constructor(player) {
|
||||
@ -105,7 +33,7 @@ class Fullscreen {
|
||||
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
||||
() => {
|
||||
// TODO: Filter for target??
|
||||
onChange.call(this);
|
||||
this.onChange();
|
||||
},
|
||||
);
|
||||
|
||||
@ -119,6 +47,9 @@ class Fullscreen {
|
||||
this.toggle();
|
||||
});
|
||||
|
||||
// Tap focus when in fullscreen
|
||||
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
||||
|
||||
// Update the UI
|
||||
this.update();
|
||||
}
|
||||
@ -188,7 +119,7 @@ class Fullscreen {
|
||||
|
||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||
|
||||
return element === this.target;
|
||||
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||
}
|
||||
|
||||
// Get target element
|
||||
@ -198,6 +129,97 @@ class Fullscreen {
|
||||
: this.player.elements.container;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update toggle button
|
||||
const button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// Trigger an event
|
||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||
}
|
||||
|
||||
toggleFallback(toggle = false) {
|
||||
// Store or restore scroll position
|
||||
if (toggle) {
|
||||
this.scrollPosition = {
|
||||
x: window.scrollX || 0,
|
||||
y: window.scrollY || 0,
|
||||
};
|
||||
} else {
|
||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||
}
|
||||
|
||||
// Toggle scroll
|
||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||
|
||||
// Toggle class hook
|
||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||
|
||||
// Force full viewport on iPhone X+
|
||||
if (browser.isIos) {
|
||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||
const property = 'viewport-fit=cover';
|
||||
|
||||
// Inject the viewport meta if required
|
||||
if (!viewport) {
|
||||
viewport = document.createElement('meta');
|
||||
viewport.setAttribute('name', 'viewport');
|
||||
}
|
||||
|
||||
// Check if the property already exists
|
||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||
|
||||
if (toggle) {
|
||||
this.cleanupViewport = !hasProperty;
|
||||
|
||||
if (!hasProperty) {
|
||||
viewport.content += `,${property}`;
|
||||
}
|
||||
} else if (this.cleanupViewport) {
|
||||
viewport.content = viewport.content
|
||||
.split(',')
|
||||
.filter(part => part.trim() !== property)
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle button and fire events
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
// Trap focus inside container
|
||||
trapFocus(event) {
|
||||
// Bail if iOS, not active, not the tab key
|
||||
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = document.activeElement;
|
||||
const focusable = getElements.call(
|
||||
this.player,
|
||||
'a[href], button:not(:disabled), input:not(:disabled), [tabindex]',
|
||||
);
|
||||
const [first] = focusable;
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
first.focus();
|
||||
event.preventDefault();
|
||||
} else if (focused === first && event.shiftKey) {
|
||||
// Move focus to last element that can be tabbed if Shift is used
|
||||
last.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
update() {
|
||||
if (this.enabled) {
|
||||
@ -230,9 +252,9 @@ class Fullscreen {
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
this.target.webkitEnterFullscreen();
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, true);
|
||||
this.toggleFallback(true);
|
||||
} else if (!this.prefix) {
|
||||
this.target.requestFullscreen();
|
||||
this.target.requestFullscreen({ navigationUI: 'hide' });
|
||||
} else if (!is.empty(this.prefix)) {
|
||||
this.target[`${this.prefix}Request${this.property}`]();
|
||||
}
|
||||
@ -247,9 +269,9 @@ class Fullscreen {
|
||||
// iOS native fullscreen
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
this.target.webkitExitFullscreen();
|
||||
this.player.play();
|
||||
silencePromise(this.player.play());
|
||||
} else if (!Fullscreen.native || this.forceFallback) {
|
||||
toggleFallback.call(this, false);
|
||||
this.toggleFallback(false);
|
||||
} else if (!this.prefix) {
|
||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||
} else if (!is.empty(this.prefix)) {
|
||||
|
@ -6,6 +6,7 @@ import support from './support';
|
||||
import { removeElement } from './utils/elements';
|
||||
import { triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { silencePromise } from './utils/promise';
|
||||
import { setAspectRatio } from './utils/style';
|
||||
|
||||
const html5 = {
|
||||
@ -30,6 +31,11 @@ const html5 = {
|
||||
|
||||
// Get quality levels
|
||||
getQualityOptions() {
|
||||
// Whether we're forcing all options (e.g. for streaming)
|
||||
if (this.config.quality.forced) {
|
||||
return this.config.quality.options;
|
||||
}
|
||||
|
||||
// Get sizes from <source> elements
|
||||
return html5.getSources
|
||||
.call(this)
|
||||
@ -37,15 +43,20 @@ const html5 = {
|
||||
.filter(Boolean);
|
||||
},
|
||||
|
||||
extend() {
|
||||
setup() {
|
||||
if (!this.isHTML5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = this;
|
||||
|
||||
// Set aspect ratio if set
|
||||
setAspectRatio.call(player);
|
||||
// Set speed options from config
|
||||
player.options.speed = player.config.speed.options;
|
||||
|
||||
// Set aspect ratio if fixed
|
||||
if (!is.empty(this.config.ratio)) {
|
||||
setAspectRatio.call(player);
|
||||
}
|
||||
|
||||
// Quality
|
||||
Object.defineProperty(player.media, 'quality', {
|
||||
@ -58,36 +69,46 @@ const html5 = {
|
||||
return source && Number(source.getAttribute('size'));
|
||||
},
|
||||
set(input) {
|
||||
// Get sources
|
||||
const sources = html5.getSources.call(player);
|
||||
// Get first match for requested size
|
||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||
|
||||
// No matching source found
|
||||
if (!source) {
|
||||
if (player.quality === input) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current state
|
||||
const { currentTime, paused, preload, readyState } = player.media;
|
||||
// If we're using an an external handler...
|
||||
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
||||
player.config.quality.onChange(input);
|
||||
} else {
|
||||
// Get sources
|
||||
const sources = html5.getSources.call(player);
|
||||
// Get first match for requested size
|
||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||
|
||||
// Set new source
|
||||
player.media.src = source.getAttribute('src');
|
||||
// No matching source found
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', () => {
|
||||
player.currentTime = currentTime;
|
||||
// Get current state
|
||||
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
||||
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
// Set new source
|
||||
player.media.src = source.getAttribute('src');
|
||||
|
||||
// Load new source
|
||||
player.media.load();
|
||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', () => {
|
||||
player.speed = playbackRate;
|
||||
player.currentTime = currentTime;
|
||||
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
silencePromise(player.play());
|
||||
}
|
||||
});
|
||||
|
||||
// Load new source
|
||||
player.media.load();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change event
|
||||
|
@ -6,9 +6,10 @@ import controls from './controls';
|
||||
import ui from './ui';
|
||||
import { repaint } from './utils/animation';
|
||||
import browser from './utils/browser';
|
||||
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||
import { getElement, getElements, matches, toggleClass } from './utils/elements';
|
||||
import { off, on, once, toggleListener, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
import { silencePromise } from './utils/promise';
|
||||
import { getAspectRatio, setAspectRatio } from './utils/style';
|
||||
|
||||
class Listeners {
|
||||
@ -99,7 +100,7 @@ class Listeners {
|
||||
case 75:
|
||||
// Space and K key
|
||||
if (!repeat) {
|
||||
player.togglePlay();
|
||||
silencePromise(player.togglePlay());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -301,14 +302,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
|
||||
const setGutter = (ratio, padding, toggle) => {
|
||||
if (!player.isVimeo) {
|
||||
@ -344,8 +337,13 @@ class Listeners {
|
||||
on.call(player, elements.container, 'enterfullscreen exitfullscreen', event => {
|
||||
const { target, usingNative } = player.fullscreen;
|
||||
|
||||
// Ignore for iOS native
|
||||
if (!player.isEmbed || target !== elements.container) {
|
||||
// Ignore events not from target
|
||||
if (target !== elements.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not an embed and no ratio specified
|
||||
if (!player.isEmbed && is.empty(player.config.ratio)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -380,19 +378,15 @@ class Listeners {
|
||||
controls.durationUpdate.call(player, event),
|
||||
);
|
||||
|
||||
// Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
on.call(player, player.media, 'canplay loadeddata', () => {
|
||||
toggleHidden(elements.volume, !player.hasAudio);
|
||||
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||
});
|
||||
|
||||
// Handle the media finishing
|
||||
on.call(player, player.media, 'ended', () => {
|
||||
// Show poster on end
|
||||
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||
// Restart
|
||||
player.restart();
|
||||
|
||||
// Call pause otherwise IE11 will start playing the video again
|
||||
player.pause();
|
||||
}
|
||||
});
|
||||
|
||||
@ -438,9 +432,21 @@ class Listeners {
|
||||
|
||||
if (player.ended) {
|
||||
this.proxy(event, player.restart, 'restart');
|
||||
this.proxy(event, player.play, 'play');
|
||||
this.proxy(
|
||||
event,
|
||||
() => {
|
||||
silencePromise(player.play());
|
||||
},
|
||||
'play',
|
||||
);
|
||||
} else {
|
||||
this.proxy(event, player.togglePlay, 'play');
|
||||
this.proxy(
|
||||
event,
|
||||
() => {
|
||||
silencePromise(player.togglePlay());
|
||||
},
|
||||
'play',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -516,7 +522,7 @@ class Listeners {
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@ -546,7 +552,14 @@ class Listeners {
|
||||
// Play/pause toggle
|
||||
if (elements.buttons.play) {
|
||||
Array.from(elements.buttons.play).forEach(button => {
|
||||
this.bind(button, 'click', player.togglePlay, 'play');
|
||||
this.bind(
|
||||
button,
|
||||
'click',
|
||||
() => {
|
||||
silencePromise(player.togglePlay());
|
||||
},
|
||||
'play',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -606,12 +619,19 @@ class Listeners {
|
||||
this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');
|
||||
|
||||
// Settings menu - click toggle
|
||||
this.bind(elements.buttons.settings, 'click', event => {
|
||||
// Prevent the document click listener closing the menu
|
||||
event.stopPropagation();
|
||||
this.bind(
|
||||
elements.buttons.settings,
|
||||
'click',
|
||||
event => {
|
||||
// Prevent the document click listener closing the menu
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
controls.toggleMenu.call(player, event);
|
||||
});
|
||||
controls.toggleMenu.call(player, event);
|
||||
},
|
||||
null,
|
||||
false,
|
||||
); // Can't be passive as we're preventing default
|
||||
|
||||
// Settings menu - keyboard toggle
|
||||
// We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
|
||||
@ -666,7 +686,7 @@ class Listeners {
|
||||
const code = event.keyCode ? event.keyCode : event.which;
|
||||
const attribute = 'play-on-seeked';
|
||||
|
||||
if (is.keyboardEvent(event) && (code !== 39 && code !== 37)) {
|
||||
if (is.keyboardEvent(event) && code !== 39 && code !== 37) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -681,7 +701,7 @@ class Listeners {
|
||||
// If we're done seeking and it was playing, resume playback
|
||||
if (play && done) {
|
||||
seek.removeAttribute(attribute);
|
||||
player.play();
|
||||
silencePromise(player.play());
|
||||
} else if (!done && player.playing) {
|
||||
seek.setAttribute(attribute, '');
|
||||
player.pause();
|
||||
@ -732,7 +752,7 @@ class Listeners {
|
||||
});
|
||||
|
||||
// Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering
|
||||
this.bind(elements.progress, 'mouseleave click', () => {
|
||||
this.bind(elements.progress, 'mouseleave touchend click', () => {
|
||||
const { previewThumbnails } = player;
|
||||
|
||||
if (previewThumbnails && previewThumbnails.loaded) {
|
||||
|
@ -39,15 +39,17 @@ const media = {
|
||||
wrap(this.media, this.elements.wrapper);
|
||||
|
||||
// Faux poster container
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster,
|
||||
});
|
||||
if (this.isEmbed) {
|
||||
this.elements.poster = createElement('div', {
|
||||
class: this.config.classNames.poster,
|
||||
});
|
||||
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
this.elements.wrapper.appendChild(this.elements.poster);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isHTML5) {
|
||||
html5.extend.call(this);
|
||||
html5.setup.call(this);
|
||||
} else if (this.isYouTube) {
|
||||
youtube.setup.call(this);
|
||||
} else if (this.isVimeo) {
|
||||
|
@ -10,7 +10,8 @@ import { createElement } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import i18n from '../utils/i18n';
|
||||
import is from '../utils/is';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import loadScript from '../utils/load-script';
|
||||
import { silencePromise } from '../utils/promise';
|
||||
import { formatTime } from '../utils/time';
|
||||
import { buildUrlParams } from '../utils/urls';
|
||||
|
||||
@ -136,7 +137,7 @@ class Ads {
|
||||
cb: Date.now(),
|
||||
AV_WIDTH: 640,
|
||||
AV_HEIGHT: 480,
|
||||
AV_CDIM2: this.publisherId,
|
||||
AV_CDIM2: config.publisherId,
|
||||
};
|
||||
|
||||
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||
@ -172,6 +173,17 @@ class Ads {
|
||||
// We assume the adContainer is the video container of the plyr element that will house the ads
|
||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media);
|
||||
|
||||
// Create ads loader
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.loader.addEventListener(
|
||||
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
||||
event => this.onAdsManagerLoaded(event),
|
||||
false,
|
||||
);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads to be pre-loaded
|
||||
this.requestAds();
|
||||
}
|
||||
@ -183,17 +195,6 @@ class Ads {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
try {
|
||||
// Create ads loader
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.loader.addEventListener(
|
||||
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
|
||||
event => this.onAdsManagerLoaded(event),
|
||||
false,
|
||||
);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads
|
||||
const request = new google.ima.AdsRequest();
|
||||
request.adTagUrl = this.tagUrl;
|
||||
@ -369,7 +370,12 @@ class Ads {
|
||||
// TODO: So there is still this thing where a video should only be allowed to start
|
||||
// playing when the IMA SDK is ready or has failed
|
||||
|
||||
this.loadAds();
|
||||
if (this.player.ended) {
|
||||
this.loadAds();
|
||||
} else {
|
||||
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||
this.loader.contentComplete();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -510,7 +516,7 @@ class Ads {
|
||||
this.playing = false;
|
||||
|
||||
// Play video
|
||||
this.player.media.play();
|
||||
silencePromise(this.player.media.play());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -563,6 +569,8 @@ class Ads {
|
||||
this.on('loaded', resolve);
|
||||
this.player.debug.log(this.manager);
|
||||
});
|
||||
// Now that the manager has been destroyed set it to also be un-initialized
|
||||
this.initialized = false;
|
||||
|
||||
// Now request some new advertisements
|
||||
this.requestAds();
|
||||
|
@ -63,6 +63,20 @@ const parseVtt = vttDataString => {
|
||||
* - This implementation uses multiple separate img elements. Other implementations use background-image on one element. This would be nice and simple, but Firefox and Safari have flickering issues with replacing backgrounds of larger images. It seems that YouTube perhaps only avoids this because they don't have the option for high-res previews (even the fullscreen ones, when mousedown/seeking). Images appear over the top of each other, and previous ones are discarded once the new ones have been rendered
|
||||
*/
|
||||
|
||||
const fitRatio = (ratio, outer) => {
|
||||
const targetRatio = outer.width / outer.height;
|
||||
const result = {};
|
||||
if (ratio > targetRatio) {
|
||||
result.width = outer.width;
|
||||
result.height = (1 / ratio) * outer.width;
|
||||
} else {
|
||||
result.height = outer.height;
|
||||
result.width = ratio * outer.height;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
class PreviewThumbnails {
|
||||
/**
|
||||
* PreviewThumbnails constructor.
|
||||
@ -90,7 +104,7 @@ class PreviewThumbnails {
|
||||
}
|
||||
|
||||
load() {
|
||||
// Togglethe regular seek tooltip
|
||||
// Toggle the regular seek tooltip
|
||||
if (this.player.elements.display.seekTooltip) {
|
||||
this.player.elements.display.seekTooltip.hidden = this.enabled;
|
||||
}
|
||||
@ -100,6 +114,10 @@ class PreviewThumbnails {
|
||||
}
|
||||
|
||||
this.getThumbnails().then(() => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render DOM elements
|
||||
this.render();
|
||||
|
||||
@ -119,19 +137,32 @@ class PreviewThumbnails {
|
||||
throw new Error('Missing previewThumbnails.src config attribute');
|
||||
}
|
||||
|
||||
// If string, convert into single-element list
|
||||
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
|
||||
const promises = urls.map(u => this.getThumbnail(u));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
// Resolve promise
|
||||
const sortAndResolve = () => {
|
||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||
this.thumbnails.sort((x, y) => x.height - y.height);
|
||||
|
||||
this.player.debug.log('Preview thumbnails', this.thumbnails);
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
// Via callback()
|
||||
if (is.function(src)) {
|
||||
src(thumbnails => {
|
||||
this.thumbnails = thumbnails;
|
||||
sortAndResolve();
|
||||
});
|
||||
}
|
||||
// VTT urls
|
||||
else {
|
||||
// If string, convert into single-element list
|
||||
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
|
||||
const promises = urls.map(u => this.getThumbnail(u));
|
||||
// Resolve
|
||||
Promise.all(promises).then(sortAndResolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -221,8 +252,8 @@ class PreviewThumbnails {
|
||||
}
|
||||
|
||||
startScrubbing(event) {
|
||||
// Only act on left mouse button (0), or touch device (event.button is false)
|
||||
if (event.button === false || event.button === 0) {
|
||||
// Only act on left mouse button (0), or touch device (event.button does not exist or is false)
|
||||
if (is.nullOrUndefined(event.button) || event.button === false || event.button === 0) {
|
||||
this.mouseDown = true;
|
||||
|
||||
// Wait until media has a duration
|
||||
@ -310,6 +341,15 @@ class PreviewThumbnails {
|
||||
this.player.elements.wrapper.appendChild(this.elements.scrubbing.container);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.elements.thumb.container) {
|
||||
this.elements.thumb.container.remove();
|
||||
}
|
||||
if (this.elements.scrubbing.container) {
|
||||
this.elements.scrubbing.container.remove();
|
||||
}
|
||||
}
|
||||
|
||||
showImageAtCurrentTime() {
|
||||
if (this.mouseDown) {
|
||||
this.setScrubbingContainerSize();
|
||||
@ -536,8 +576,16 @@ class PreviewThumbnails {
|
||||
|
||||
get thumbContainerHeight() {
|
||||
if (this.mouseDown) {
|
||||
// Can't use media.clientHeight - HTML5 video goes big and does black bars above and below
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio);
|
||||
const { height } = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight,
|
||||
});
|
||||
return height;
|
||||
}
|
||||
|
||||
// If css is used this needs to return the css height for sprites to work (see setImageSizeAndOffset)
|
||||
if (this.sizeSpecifiedInCSS) {
|
||||
return this.elements.thumb.imageContainer.clientHeight;
|
||||
}
|
||||
|
||||
return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio / 4);
|
||||
@ -580,7 +628,7 @@ class PreviewThumbnails {
|
||||
}
|
||||
|
||||
determineContainerAutoSizing() {
|
||||
if (this.elements.thumb.imageContainer.clientHeight > 20) {
|
||||
if (this.elements.thumb.imageContainer.clientHeight > 20 || this.elements.thumb.imageContainer.clientWidth > 20) {
|
||||
// This will prevent auto sizing in this.setThumbContainerSizeAndPos()
|
||||
this.sizeSpecifiedInCSS = true;
|
||||
}
|
||||
@ -592,6 +640,12 @@ class PreviewThumbnails {
|
||||
const thumbWidth = Math.floor(this.thumbContainerHeight * this.thumbAspectRatio);
|
||||
this.elements.thumb.imageContainer.style.height = `${this.thumbContainerHeight}px`;
|
||||
this.elements.thumb.imageContainer.style.width = `${thumbWidth}px`;
|
||||
} else if (this.elements.thumb.imageContainer.clientHeight > 20 && this.elements.thumb.imageContainer.clientWidth < 20) {
|
||||
const thumbWidth = Math.floor(this.elements.thumb.imageContainer.clientHeight * this.thumbAspectRatio);
|
||||
this.elements.thumb.imageContainer.style.width = `${thumbWidth}px`;
|
||||
} else if (this.elements.thumb.imageContainer.clientHeight < 20 && this.elements.thumb.imageContainer.clientWidth > 20) {
|
||||
const thumbHeight = Math.floor(this.elements.thumb.imageContainer.clientWidth / this.thumbAspectRatio);
|
||||
this.elements.thumb.imageContainer.style.height = `${thumbHeight}px`;
|
||||
}
|
||||
|
||||
this.setThumbContainerPos();
|
||||
@ -620,9 +674,12 @@ class PreviewThumbnails {
|
||||
|
||||
// Can't use 100% width, in case the video is a different aspect ratio to the video container
|
||||
setScrubbingContainerSize() {
|
||||
this.elements.scrubbing.container.style.width = `${this.player.media.clientWidth}px`;
|
||||
// Can't use media.clientHeight - html5 video goes big and does black bars above and below
|
||||
this.elements.scrubbing.container.style.height = `${this.player.media.clientWidth / this.thumbAspectRatio}px`;
|
||||
const { width, height } = fitRatio(this.thumbAspectRatio, {
|
||||
width: this.player.media.clientWidth,
|
||||
height: this.player.media.clientHeight,
|
||||
});
|
||||
this.elements.scrubbing.container.style.width = `${width}px`;
|
||||
this.elements.scrubbing.container.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
// Sprites need to be offset to the correct location
|
||||
@ -635,9 +692,9 @@ class PreviewThumbnails {
|
||||
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 = `${previewImage.naturalHeight * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
|
||||
previewImage.style.width = `${previewImage.naturalWidth * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
previewImage.style.left = `-${frame.x * multiplier}px`;
|
||||
// eslint-disable-next-line no-param-reassign
|
@ -9,7 +9,7 @@ import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import fetch from '../utils/fetch';
|
||||
import is from '../utils/is';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import loadScript from '../utils/load-script';
|
||||
import { extend } from '../utils/objects';
|
||||
import { format, stripHTML } from '../utils/strings';
|
||||
import { setAspectRatio } from '../utils/style';
|
||||
@ -42,23 +42,28 @@ function assurePlaybackState(play) {
|
||||
|
||||
const vimeo = {
|
||||
setup() {
|
||||
const player = this;
|
||||
|
||||
// Add embed class for responsive
|
||||
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
|
||||
|
||||
// Set speed options from config
|
||||
player.options.speed = player.config.speed.options;
|
||||
|
||||
// Set intial ratio
|
||||
setAspectRatio.call(this);
|
||||
setAspectRatio.call(player);
|
||||
|
||||
// Load the SDK if not already
|
||||
if (!is.object(window.Vimeo)) {
|
||||
loadScript(this.config.urls.vimeo.sdk)
|
||||
loadScript(player.config.urls.vimeo.sdk)
|
||||
.then(() => {
|
||||
vimeo.ready.call(this);
|
||||
vimeo.ready.call(player);
|
||||
})
|
||||
.catch(error => {
|
||||
this.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
||||
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
||||
});
|
||||
} else {
|
||||
vimeo.ready.call(this);
|
||||
vimeo.ready.call(player);
|
||||
}
|
||||
},
|
||||
|
||||
@ -99,6 +104,11 @@ const vimeo = {
|
||||
iframe.setAttribute('allowtransparency', '');
|
||||
iframe.setAttribute('allow', 'autoplay');
|
||||
|
||||
// Set the referrer policy if required
|
||||
if (!is.empty(config.referrerPolicy)) {
|
||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
||||
}
|
||||
|
||||
// Get poster, if already set
|
||||
const { poster } = player;
|
||||
// Inject the package
|
||||
@ -191,18 +201,13 @@ const vimeo = {
|
||||
return speed;
|
||||
},
|
||||
set(input) {
|
||||
player.embed
|
||||
.setPlaybackRate(input)
|
||||
.then(() => {
|
||||
speed = input;
|
||||
triggerEvent.call(player, player.media, 'ratechange');
|
||||
})
|
||||
.catch(error => {
|
||||
// Hide menu item (and menu if empty)
|
||||
if (error.name === 'Error') {
|
||||
controls.setSpeedMenu.call(player, []);
|
||||
}
|
||||
});
|
||||
player.embed.setPlaybackRate(input).then(() => {
|
||||
speed = input;
|
||||
triggerEvent.call(player, player.media, 'ratechange');
|
||||
}).catch(() => {
|
||||
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||
player.options.speed = [1];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -335,6 +340,14 @@ const vimeo = {
|
||||
}
|
||||
});
|
||||
|
||||
player.embed.on('bufferstart', () => {
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
});
|
||||
|
||||
player.embed.on('bufferend', () => {
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
});
|
||||
|
||||
player.embed.on('play', () => {
|
||||
assurePlaybackState.call(player, true);
|
||||
triggerEvent.call(player, player.media, 'playing');
|
||||
|
@ -7,8 +7,8 @@ import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import fetch from '../utils/fetch';
|
||||
import is from '../utils/is';
|
||||
import loadImage from '../utils/loadImage';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import loadImage from '../utils/load-image';
|
||||
import loadScript from '../utils/load-script';
|
||||
import { extend } from '../utils/objects';
|
||||
import { format, generateId } from '../utils/strings';
|
||||
import { setAspectRatio } from '../utils/style';
|
||||
@ -297,7 +297,9 @@ const youtube = {
|
||||
});
|
||||
|
||||
// Get available speeds
|
||||
player.options.speed = instance.getAvailablePlaybackRates();
|
||||
const speeds = instance.getAvailablePlaybackRates();
|
||||
// Filter based on config
|
||||
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
||||
|
||||
// Set the tabindex to avoid focus entering iframe
|
||||
if (player.supported.ui) {
|
||||
@ -416,6 +418,12 @@ const youtube = {
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Trigger waiting event to add loading classes to container as the video buffers.
|
||||
triggerEvent.call(player, player.media, 'waiting');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
620
src/js/plyr.d.ts
vendored
Normal file
620
src/js/plyr.d.ts
vendored
Normal file
@ -0,0 +1,620 @@
|
||||
// Type definitions for plyr 3.5
|
||||
// Project: https://plyr.io
|
||||
// Definitions by: ondratra <https://github.com/ondratra>
|
||||
// TypeScript Version: 3.0
|
||||
|
||||
export = Plyr;
|
||||
export as namespace Plyr;
|
||||
|
||||
declare class Plyr {
|
||||
/**
|
||||
* Setup a new instance
|
||||
*/
|
||||
static setup(targets: NodeList | HTMLElement | HTMLElement[] | string, options?: Plyr.Options): Plyr[];
|
||||
|
||||
/**
|
||||
* Check for support
|
||||
* @param mediaType
|
||||
* @param provider
|
||||
* @param playsInline Whether the player has the playsinline attribute (only applicable to iOS 10+)
|
||||
*/
|
||||
static supported(mediaType?: Plyr.MediaType, provider?: Plyr.Provider, playsInline?: boolean): Plyr.Support;
|
||||
|
||||
constructor(targets: NodeList | HTMLElement | HTMLElement[] | string, options?: Plyr.Options);
|
||||
|
||||
/**
|
||||
* Indicates if the current player is HTML5.
|
||||
*/
|
||||
readonly isHTML5: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is an embedded player.
|
||||
*/
|
||||
readonly isEmbed: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is playing.
|
||||
*/
|
||||
readonly playing: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is paused.
|
||||
*/
|
||||
readonly paused: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is stopped.
|
||||
*/
|
||||
readonly stopped: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player has finished playback.
|
||||
*/
|
||||
readonly ended: boolean;
|
||||
|
||||
/**
|
||||
* Returns a float between 0 and 1 indicating how much of the media is buffered
|
||||
*/
|
||||
readonly buffered: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the currentTime for the player. The setter accepts a float in seconds.
|
||||
*/
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* Indicates if the current player is seeking.
|
||||
*/
|
||||
readonly seeking: boolean;
|
||||
|
||||
/**
|
||||
* Returns the duration for the current media.
|
||||
*/
|
||||
readonly duration: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the volume for the player. The setter accepts a float between 0 and 1.
|
||||
*/
|
||||
volume: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the muted state of the player. The setter accepts a boolean.
|
||||
*/
|
||||
muted: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current media has an audio track.
|
||||
*/
|
||||
readonly hasAudio: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5.
|
||||
*/
|
||||
speed: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the quality for the player. The setter accepts a value from the options specified in your config.
|
||||
*/
|
||||
quality: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the current loop state of the player.
|
||||
*/
|
||||
loop: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the current source for the player.
|
||||
*/
|
||||
source: Plyr.SourceInfo;
|
||||
|
||||
/**
|
||||
* Gets or sets the current poster image URL for the player.
|
||||
*/
|
||||
poster: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the autoplay state of the player.
|
||||
*/
|
||||
autoplay: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the caption track by index. 1 means the track is missing or captions is not active
|
||||
*/
|
||||
currentTrack: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the preferred captions language for the player. The setter accepts an ISO twoletter 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: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the picture-in-picture state of the player. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+.
|
||||
*/
|
||||
pip: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets the aspect ratio for embedded players.
|
||||
*/
|
||||
ratio?: string;
|
||||
|
||||
/**
|
||||
* Returns the current video Provider
|
||||
*/
|
||||
readonly provider: 'html5' | 'vimeo' | 'youtube';
|
||||
|
||||
/**
|
||||
* Returns the native API for Vimeo or Youtube players
|
||||
*/
|
||||
readonly embed?: any;
|
||||
|
||||
readonly fullscreen: Plyr.FullscreenControl;
|
||||
|
||||
/**
|
||||
* Start playback.
|
||||
* For HTML5 players, play() will return a Promise in some browsers - WebKit and Mozilla according to MDN at time of writing.
|
||||
*/
|
||||
play(): Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Pause playback.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Toggle playback, if no parameters are passed, it will toggle based on current status.
|
||||
*/
|
||||
togglePlay(toggle?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Stop playback and reset to start.
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* Restart playback.
|
||||
*/
|
||||
restart(): void;
|
||||
|
||||
/**
|
||||
* Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used.
|
||||
*/
|
||||
rewind(seekTime?: number): void;
|
||||
|
||||
/**
|
||||
* Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used.
|
||||
*/
|
||||
forward(seekTime?: number): void;
|
||||
|
||||
/**
|
||||
* Increase volume by the specified step. If no parameter is passed, the default step will be used.
|
||||
*/
|
||||
increaseVolume(step?: number): void;
|
||||
|
||||
/**
|
||||
* Increase volume by the specified step. If no parameter is passed, the default step will be used.
|
||||
*/
|
||||
decreaseVolume(step?: number): void;
|
||||
|
||||
/**
|
||||
* Toggle captions display. If no parameter is passed, it will toggle based on current status.
|
||||
*/
|
||||
toggleCaptions(toggle?: boolean): void;
|
||||
|
||||
/**
|
||||
* Trigger the airplay dialog on supported devices.
|
||||
*/
|
||||
airplay(): void;
|
||||
|
||||
/**
|
||||
* Toggle the controls (video only). Takes optional truthy value to force it on/off.
|
||||
*/
|
||||
toggleControls(toggle: boolean): void;
|
||||
|
||||
/**
|
||||
* Add an event listener for the specified event.
|
||||
*/
|
||||
on(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Add an event listener for the specified event once.
|
||||
*/
|
||||
once(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Remove an event listener for the specified event.
|
||||
*/
|
||||
off(
|
||||
event: Plyr.StandardEvent | Plyr.Html5Event | Plyr.YoutubeEvent,
|
||||
callback: (this: this, event: Plyr.PlyrEvent) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Check support for a mime type.
|
||||
*/
|
||||
supports(type: string): boolean;
|
||||
|
||||
/**
|
||||
* Destroy lib instance
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
declare namespace Plyr {
|
||||
type MediaType = 'audio' | 'video';
|
||||
type Provider = 'html5' | 'youtube' | 'vimeo';
|
||||
type StandardEvent =
|
||||
| 'progress'
|
||||
| 'playing'
|
||||
| 'play'
|
||||
| 'pause'
|
||||
| 'timeupdate'
|
||||
| 'volumechange'
|
||||
| 'seeking'
|
||||
| 'seeked'
|
||||
| 'ratechange'
|
||||
| 'ended'
|
||||
| 'enterfullscreen'
|
||||
| 'exitfullscreen'
|
||||
| 'captionsenabled'
|
||||
| 'captionsdisabled'
|
||||
| 'languagechange'
|
||||
| 'controlshidden'
|
||||
| 'controlsshown'
|
||||
| 'ready';
|
||||
type Html5Event =
|
||||
| 'loadstart'
|
||||
| 'loadeddata'
|
||||
| 'loadedmetadata'
|
||||
| 'canplay'
|
||||
| 'canplaythrough'
|
||||
| 'stalled'
|
||||
| 'waiting'
|
||||
| 'emptied'
|
||||
| 'cuechange'
|
||||
| 'error';
|
||||
type YoutubeEvent = 'statechange' | 'qualitychange' | 'qualityrequested';
|
||||
|
||||
interface FullscreenControl {
|
||||
/**
|
||||
* Indicates if the current player is in fullscreen mode.
|
||||
*/
|
||||
readonly active: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the current player has fullscreen enabled.
|
||||
*/
|
||||
readonly enabled: boolean;
|
||||
|
||||
/**
|
||||
* Enter fullscreen. If fullscreen is not supported, a fallback ""full window/viewport"" is used instead.
|
||||
*/
|
||||
enter(): void;
|
||||
|
||||
/**
|
||||
* Exit fullscreen.
|
||||
*/
|
||||
exit(): void;
|
||||
|
||||
/**
|
||||
* Toggle fullscreen.
|
||||
*/
|
||||
toggle(): void;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Display debugging information in the console
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* 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 for more info on how the html needs to be structured.
|
||||
* Defaults to ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']
|
||||
*/
|
||||
controls?: string[] | ((id: string, seektime: number, title: string) => unknown) | Element;
|
||||
|
||||
/**
|
||||
* If you're using the default controls are used then you can specify which settings to show in the menu
|
||||
* Defaults to ['captions', 'quality', 'speed', 'loop']
|
||||
*/
|
||||
settings?: string[];
|
||||
|
||||
/**
|
||||
* Used for internationalization (i18n) of the text within the UI.
|
||||
*/
|
||||
i18n?: any;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Specify a URL or path to the SVG sprite. See the SVG section for more info.
|
||||
*/
|
||||
iconUrl?: string;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Specify a URL or path to a blank video file used to properly cancel network requests.
|
||||
*/
|
||||
blankUrl?: string;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Only allow one player playing at once.
|
||||
*/
|
||||
autopause?: boolean;
|
||||
|
||||
/**
|
||||
* The time, in seconds, to seek when a user hits fast forward or rewind.
|
||||
*/
|
||||
seekTime?: number;
|
||||
|
||||
/**
|
||||
* A number, between 0 and 1, representing the initial volume of the player.
|
||||
*/
|
||||
volume?: number;
|
||||
|
||||
/**
|
||||
* Whether to start playback muted. If the muted attribute is present on a <video> or <audio> element, this will be automatically set to true.
|
||||
*/
|
||||
muted?: boolean;
|
||||
|
||||
/**
|
||||
* Click (or tap) of the video container will toggle play/pause.
|
||||
*/
|
||||
clickToPlay?: boolean;
|
||||
|
||||
/**
|
||||
* Disable right click menu on video to help as very primitive obfuscation to prevent downloads of content.
|
||||
*/
|
||||
disableContextMenu?: boolean;
|
||||
|
||||
/**
|
||||
* Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen.
|
||||
* As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly.
|
||||
*/
|
||||
hideControls?: boolean;
|
||||
|
||||
/**
|
||||
* Reset the playback to the start once playback is complete.
|
||||
*/
|
||||
resetOnEnd?: boolean;
|
||||
|
||||
/**
|
||||
* Enable keyboard shortcuts for focused players only or globally
|
||||
*/
|
||||
keyboard?: KeyboardOptions;
|
||||
|
||||
/**
|
||||
* controls: Display control labels as tooltips on :hover & :focus (by default, the labels are screen reader only).
|
||||
* seek: Display a seek tooltip to indicate on click where the media would seek to.
|
||||
*/
|
||||
tooltips?: TooltipOptions;
|
||||
|
||||
/**
|
||||
* Specify a custom duration for media.
|
||||
*/
|
||||
duration?: number;
|
||||
|
||||
/**
|
||||
* Displays the duration of the media on the metadataloaded event (on startup) in the current time display.
|
||||
* This will only work if the preload attribute is not set to none (or is not set at all) and you choose not to display the duration (see controls option).
|
||||
*/
|
||||
displayDuration?: boolean;
|
||||
|
||||
/**
|
||||
* Display the current time as a countdown rather than an incremental counter.
|
||||
*/
|
||||
invertTime?: boolean;
|
||||
|
||||
/**
|
||||
* Allow users to click to toggle the above.
|
||||
*/
|
||||
toggleInvert?: boolean;
|
||||
|
||||
/**
|
||||
* 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?: { [key: string]: (error: PlyrEvent) => void };
|
||||
|
||||
/**
|
||||
* 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?: CaptionOptions;
|
||||
|
||||
/**
|
||||
* enabled: Toggles whether fullscreen should be enabled. fallback: Allow fallback to a full-window solution.
|
||||
* iosNative: whether to use native iOS fullscreen when entering fullscreen (no custom controls)
|
||||
*/
|
||||
fullscreen?: FullScreenOptions;
|
||||
|
||||
/**
|
||||
* The aspect ratio you want to use for embedded players.
|
||||
*/
|
||||
ratio?: string;
|
||||
|
||||
/**
|
||||
* enabled: Allow use of local storage to store user settings. key: The key name to use.
|
||||
*/
|
||||
storage?: StorageOptions;
|
||||
|
||||
/**
|
||||
* selected: The default speed for playback. options: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically.
|
||||
*/
|
||||
speed?: SpeedOptions;
|
||||
|
||||
/**
|
||||
* Currently only supported by YouTube. default is the default quality level, determined by YouTube. options are the options to display.
|
||||
*/
|
||||
quality?: QualityOptions;
|
||||
|
||||
/**
|
||||
* 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?: LoopOptions;
|
||||
|
||||
/**
|
||||
* enabled: Whether to enable vi.ai ads. publisherId: Your unique vi.ai publisher ID.
|
||||
*/
|
||||
ads?: AdOptions;
|
||||
|
||||
/**
|
||||
* Vimeo Player Options.
|
||||
*/
|
||||
vimeo?: object;
|
||||
|
||||
/**
|
||||
* Youtube Player Options.
|
||||
*/
|
||||
youtube?: object;
|
||||
}
|
||||
|
||||
interface QualityOptions {
|
||||
default: number;
|
||||
options: number[];
|
||||
}
|
||||
|
||||
interface LoopOptions {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
interface AdOptions {
|
||||
enabled: boolean;
|
||||
publisherId: string;
|
||||
}
|
||||
|
||||
interface SpeedOptions {
|
||||
selected: number;
|
||||
options: number[];
|
||||
}
|
||||
|
||||
interface KeyboardOptions {
|
||||
focused?: boolean;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
interface TooltipOptions {
|
||||
controls?: boolean;
|
||||
seek?: boolean;
|
||||
}
|
||||
|
||||
interface FullScreenOptions {
|
||||
enabled?: boolean;
|
||||
fallback?: boolean;
|
||||
allowAudio?: boolean;
|
||||
iosNative?: boolean;
|
||||
}
|
||||
|
||||
interface CaptionOptions {
|
||||
active?: boolean;
|
||||
language?: string;
|
||||
update?: boolean;
|
||||
}
|
||||
|
||||
interface StorageOptions {
|
||||
enabled?: boolean;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
interface SourceInfo {
|
||||
/**
|
||||
* Note: YouTube and Vimeo are currently not supported as audio sources.
|
||||
*/
|
||||
type: MediaType;
|
||||
|
||||
/**
|
||||
* Title of the new media. Used for the aria-label attribute on the play button, and outer container. YouTube and Vimeo are populated automatically.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required.
|
||||
*/
|
||||
sources: Source[];
|
||||
|
||||
/**
|
||||
* The URL for the poster image (HTML5 video only).
|
||||
*/
|
||||
poster?: string;
|
||||
|
||||
/**
|
||||
* An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above,
|
||||
* it will render as <track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default> and similar for the French version.
|
||||
* Booleans are converted to HTML5 value-less attributes.
|
||||
*/
|
||||
tracks?: Track[];
|
||||
}
|
||||
|
||||
interface Source {
|
||||
/**
|
||||
* The URL of the media file (or YouTube/Vimeo URL).
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* The MIME type of the media file (if HTML5).
|
||||
*/
|
||||
type?: string;
|
||||
provider?: Provider;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
type TrackKind = 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
|
||||
interface Track {
|
||||
/**
|
||||
* Indicates how the text track is meant to be used
|
||||
*/
|
||||
kind: TrackKind;
|
||||
/**
|
||||
* Indicates a user-readable title for the track
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The language of the track text data. It must be a valid BCP 47 language tag. If the kind attribute is set to subtitles, then srclang must be defined.
|
||||
*/
|
||||
srcLang?: string;
|
||||
/**
|
||||
* The URL of the track (.vtt file).
|
||||
*/
|
||||
src: string;
|
||||
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
interface PlyrEvent extends CustomEvent {
|
||||
readonly detail: { readonly plyr: Plyr };
|
||||
}
|
||||
|
||||
interface Support {
|
||||
api: boolean;
|
||||
ui: boolean;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.5.3
|
||||
// plyr.js v3.5.10
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
@ -15,7 +15,7 @@ import Fullscreen from './fullscreen';
|
||||
import Listeners from './listeners';
|
||||
import media from './media';
|
||||
import Ads from './plugins/ads';
|
||||
import PreviewThumbnails from './plugins/previewThumbnails';
|
||||
import PreviewThumbnails from './plugins/preview-thumbnails';
|
||||
import source from './source';
|
||||
import Storage from './storage';
|
||||
import support from './support';
|
||||
@ -24,9 +24,10 @@ import { closest } from './utils/arrays';
|
||||
import { createElement, hasClass, removeElement, replaceElement, toggleClass, wrap } from './utils/elements';
|
||||
import { off, on, once, triggerEvent, unbindListeners } from './utils/events';
|
||||
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 { silencePromise } from './utils/promise';
|
||||
import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style';
|
||||
import { parseUrl } from './utils/urls';
|
||||
|
||||
@ -303,7 +304,7 @@ class Plyr {
|
||||
|
||||
// Autoplay if required
|
||||
if (this.isHTML5 && this.config.autoplay) {
|
||||
setTimeout(() => this.play(), 10);
|
||||
setTimeout(() => silencePromise(this.play()), 10);
|
||||
}
|
||||
|
||||
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||
@ -356,7 +357,7 @@ class Plyr {
|
||||
|
||||
// Intecept play with ads
|
||||
if (this.ads && this.ads.enabled) {
|
||||
this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
||||
this.ads.managerPromise.then(() => this.ads.play()).catch(() => silencePromise(this.media.play()));
|
||||
}
|
||||
|
||||
// Return the promise (for HTML5)
|
||||
@ -368,10 +369,10 @@ class Plyr {
|
||||
*/
|
||||
pause() {
|
||||
if (!this.playing || !is.function(this.media.pause)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.media.pause();
|
||||
return this.media.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -411,10 +412,10 @@ class Plyr {
|
||||
const toggle = is.boolean(input) ? input : !this.playing;
|
||||
|
||||
if (toggle) {
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
return this.play();
|
||||
}
|
||||
|
||||
return this.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,7 +442,7 @@ class Plyr {
|
||||
* @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
|
||||
*/
|
||||
rewind(seekTime) {
|
||||
this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime -= is.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -449,7 +450,7 @@ class Plyr {
|
||||
* @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
|
||||
*/
|
||||
forward(seekTime) {
|
||||
this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime);
|
||||
this.currentTime += is.number(seekTime) ? seekTime : this.config.seekTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.5.3
|
||||
// plyr.js v3.5.10
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
@ -5,6 +5,7 @@
|
||||
import { providers } from './config/types';
|
||||
import html5 from './html5';
|
||||
import media from './media';
|
||||
import PreviewThumbnails from './plugins/preview-thumbnails';
|
||||
import support from './support';
|
||||
import ui from './ui';
|
||||
import { createElement, insertElement, removeElement } from './utils/elements';
|
||||
@ -130,9 +131,20 @@ const source = {
|
||||
this.media.load();
|
||||
}
|
||||
|
||||
// Reload thumbnails
|
||||
if (this.previewThumbnails) {
|
||||
this.previewThumbnails.load();
|
||||
// Update previewThumbnails config & reload plugin
|
||||
if (!is.empty(input.previewThumbnails)) {
|
||||
Object.assign(this.config.previewThumbnails, input.previewThumbnails);
|
||||
|
||||
// Cleanup previewThumbnails plugin if it was loaded
|
||||
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
||||
this.previewThumbnails.destroy();
|
||||
this.previewThumbnails = null;
|
||||
}
|
||||
|
||||
// Create new instance if it is still enabled
|
||||
if (this.config.previewThumbnails.enabled) {
|
||||
this.previewThumbnails = new PreviewThumbnails(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the fullscreen support
|
||||
|
10
src/js/ui.js
10
src/js/ui.js
@ -10,7 +10,7 @@ import { getElement, toggleClass } from './utils/elements';
|
||||
import { ready, triggerEvent } from './utils/events';
|
||||
import i18n from './utils/i18n';
|
||||
import is from './utils/is';
|
||||
import loadImage from './utils/loadImage';
|
||||
import loadImage from './utils/load-image';
|
||||
|
||||
const ui = {
|
||||
addStyleHook() {
|
||||
@ -172,6 +172,11 @@ const ui = {
|
||||
// Set property synchronously to respect the call order
|
||||
this.media.setAttribute('poster', poster);
|
||||
|
||||
// HTML5 uses native poster attribute
|
||||
if (this.isHTML5) {
|
||||
return Promise.resolve(poster);
|
||||
}
|
||||
|
||||
// Wait until ui is ready
|
||||
return (
|
||||
ready
|
||||
@ -198,7 +203,9 @@ const ui = {
|
||||
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
||||
backgroundSize: '',
|
||||
});
|
||||
|
||||
ui.togglePoster.call(this, true);
|
||||
|
||||
return poster;
|
||||
})
|
||||
);
|
||||
@ -214,6 +221,7 @@ const ui = {
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||
Object.assign(target, { pressed: this.playing });
|
||||
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
||||
});
|
||||
|
||||
// Only update controls on non timeupdate events
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Animation utils
|
||||
// ==========================================================================
|
||||
|
||||
import { toggleHidden } from './elements';
|
||||
import is from './is';
|
||||
|
||||
export const transitionEndEvent = (() => {
|
||||
@ -21,14 +20,19 @@ export const transitionEndEvent = (() => {
|
||||
})();
|
||||
|
||||
// Force repaint of element
|
||||
export function repaint(element) {
|
||||
export function repaint(element, delay) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
toggleHidden(element, true);
|
||||
element.offsetHeight; // eslint-disable-line
|
||||
toggleHidden(element, false);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
element.hidden = true;
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
element.offsetHeight;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
element.hidden = false;
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
}, 0);
|
||||
}, delay);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Element utils
|
||||
// ==========================================================================
|
||||
|
||||
import { toggleListener } from './events';
|
||||
import is from './is';
|
||||
import { extend } from './objects';
|
||||
|
||||
@ -192,11 +191,8 @@ export function toggleHidden(element, hidden) {
|
||||
hide = !element.hidden;
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
element.setAttribute('hidden', '');
|
||||
} else {
|
||||
element.removeAttribute('hidden');
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
element.hidden = hide;
|
||||
}
|
||||
|
||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||
@ -225,7 +221,7 @@ export function hasClass(element, className) {
|
||||
|
||||
// Element matches selector
|
||||
export function matches(element, selector) {
|
||||
const prototype = { Element };
|
||||
const {prototype} = Element;
|
||||
|
||||
function match() {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
@ -251,39 +247,6 @@ export function getElement(selector) {
|
||||
return this.elements.container.querySelector(selector);
|
||||
}
|
||||
|
||||
// Trap focus inside container
|
||||
export function trapFocus(element = null, toggle = false) {
|
||||
if (!is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
const trap = event => {
|
||||
// Bail if not tab key or not fullscreen
|
||||
if (event.key !== 'Tab' || event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = document.activeElement;
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
first.focus();
|
||||
event.preventDefault();
|
||||
} else if (focused === first && event.shiftKey) {
|
||||
// Move focus to last element that can be tabbed if Shift is used
|
||||
last.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
|
||||
}
|
||||
|
||||
// Set focus and tab focus class
|
||||
export function setFocus(element = null, tabFocus = false) {
|
||||
if (!is.element(element)) {
|
||||
|
@ -90,9 +90,7 @@ export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
||||
// Create and dispatch the event
|
||||
const event = new CustomEvent(type, {
|
||||
bubbles,
|
||||
detail: Object.assign({}, detail, {
|
||||
plyr: this,
|
||||
}),
|
||||
detail: { ...detail, plyr: this,},
|
||||
});
|
||||
|
||||
// Dispatch the event
|
||||
|
@ -19,7 +19,7 @@ const isEvent = input => instanceOf(input, Event);
|
||||
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
||||
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
||||
const isPromise = input => instanceOf(input, Promise);
|
||||
const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
|
||||
|
||||
const isEmpty = input =>
|
||||
isNullOrUndefined(input) ||
|
||||
|
14
src/js/utils/promise.js
Normal file
14
src/js/utils/promise.js
Normal file
@ -0,0 +1,14 @@
|
||||
import is from './is';
|
||||
/**
|
||||
* Silence a Promise-like object.
|
||||
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||
* play promise" rejection error messages.
|
||||
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||
*/
|
||||
export function silencePromise(value) {
|
||||
if (is.promise(value)) {
|
||||
value.then(null, () => {});
|
||||
}
|
||||
}
|
||||
|
||||
export default { silencePromise };
|
@ -27,15 +27,8 @@ export function reduceAspectRatio(ratio) {
|
||||
}
|
||||
|
||||
export function getAspectRatio(input) {
|
||||
const parse = ratio => {
|
||||
if (!validateRatio(ratio)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ratio.split(':').map(Number);
|
||||
};
|
||||
|
||||
// Provided ratio
|
||||
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
||||
// Try provided ratio
|
||||
let ratio = parse(input);
|
||||
|
||||
// Get from config
|
||||
@ -63,11 +56,12 @@ export function setAspectRatio(input) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { wrapper } = this.elements;
|
||||
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}%`;
|
||||
wrapper.style.paddingBottom = `${padding}%`;
|
||||
|
||||
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||
if (this.isVimeo && this.supported.ui) {
|
||||
@ -75,7 +69,7 @@ export function setAspectRatio(input) {
|
||||
const offset = (height - padding) / (height / 50);
|
||||
this.media.style.transform = `translateY(-${offset}%)`;
|
||||
} else if (this.isHTML5) {
|
||||
this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||
}
|
||||
|
||||
return { padding, ratio };
|
||||
|
@ -13,7 +13,7 @@ export const getSeconds = value => Math.trunc(value % 60, 10);
|
||||
export function formatTime(time = 0, displayHours = false, inverted = false) {
|
||||
// Bail if the value isn't a number
|
||||
if (!is.number(time)) {
|
||||
return formatTime(null, displayHours, inverted);
|
||||
return formatTime(undefined, displayHours, inverted);
|
||||
}
|
||||
|
||||
// Format time component to add leading zero
|
||||
|
@ -5,24 +5,28 @@
|
||||
// Base
|
||||
.plyr {
|
||||
@include plyr-font-smoothing($plyr-font-smoothing);
|
||||
|
||||
align-items: center;
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--plyr-font-family);
|
||||
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
||||
font-weight: var(--plyr-font-weight-regular);
|
||||
height: 100%;
|
||||
line-height: var(--plyr-line-height);
|
||||
max-width: 100%;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
text-shadow: none;
|
||||
transition: box-shadow 0.3s ease;
|
||||
z-index: 0; // Force any border radius
|
||||
|
||||
// Media elements
|
||||
video,
|
||||
audio {
|
||||
border-radius: inherit;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
audio,
|
||||
iframe {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,10 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__badge {
|
||||
background: $plyr-badge-bg;
|
||||
background: var(--plyr-badge-background, $plyr-badge-background);
|
||||
border-radius: 2px;
|
||||
color: $plyr-badge-color;
|
||||
font-size: var(--plyr-font-size-badge);
|
||||
color: var(--plyr-badge-color, $plyr-badge-color);
|
||||
font-size: var(--plyr-font-size-badge, $plyr-font-size-badge);
|
||||
line-height: 1;
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
.plyr__captions {
|
||||
animation: plyr-fade-in 0.3s ease;
|
||||
bottom: 0;
|
||||
color: var(--plyr-captions-text-color);
|
||||
display: none;
|
||||
font-size: $plyr-font-size-captions-small;
|
||||
left: 0;
|
||||
@ -21,9 +20,12 @@
|
||||
width: 100%;
|
||||
|
||||
.plyr__caption {
|
||||
background: var(--plyr-captions-background);
|
||||
background: $plyr-captions-background;
|
||||
background: var(--plyr-captions-background, $plyr-captions-background);
|
||||
border-radius: 2px;
|
||||
box-decoration-break: clone;
|
||||
color: $plyr-captions-text-color;
|
||||
color: var(--plyr-captions-text-color, $plyr-captions-text-color);
|
||||
line-height: 185%;
|
||||
padding: 0.2em 0.5em;
|
||||
white-space: pre-wrap;
|
||||
|
@ -5,21 +5,22 @@
|
||||
.plyr__control {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: var(--plyr-control-radius);
|
||||
border-radius: $plyr-control-radius;
|
||||
border-radius: var(--plyr-control-radius, $plyr-control-radius);
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
overflow: visible; // IE11
|
||||
padding: var(--plyr-control-padding);
|
||||
padding: var(--plyr-control-padding, $plyr-control-padding);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
fill: currentColor;
|
||||
height: var(--plyr-control-icon-size);
|
||||
height: var(--plyr-control-icon-size, $plyr-control-icon-size);
|
||||
pointer-events: none;
|
||||
width: var(--plyr-control-icon-size);
|
||||
width: var(--plyr-control-icon-size, $plyr-control-icon-size);
|
||||
}
|
||||
|
||||
// Default focus
|
||||
@ -50,64 +51,3 @@ a.plyr__control {
|
||||
.plyr__control.plyr__control--pressed .label--not-pressed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Audio control
|
||||
.plyr--audio .plyr__control {
|
||||
&.plyr__tab-focus,
|
||||
&:hover,
|
||||
&[aria-expanded='true'] {
|
||||
background: $plyr-audio-control-bg-hover;
|
||||
color: $plyr-audio-control-color-hover;
|
||||
}
|
||||
}
|
||||
|
||||
// Video control
|
||||
.plyr--video .plyr__control {
|
||||
svg {
|
||||
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
||||
}
|
||||
|
||||
// Hover and tab focus
|
||||
&.plyr__tab-focus,
|
||||
&:hover,
|
||||
&[aria-expanded='true'] {
|
||||
background: var(--plyr-video-control-bg-hover);
|
||||
color: var(--plyr-video-control-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
// Large play button (video only)
|
||||
.plyr__control--overlaid {
|
||||
background: var(--plyr-video-control-bg-hover);
|
||||
border: 0;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 1px 1px rgba(#000, 0.15);
|
||||
color: var(--plyr-video-control-color-hover);
|
||||
display: none;
|
||||
left: 50%;
|
||||
padding: ceil($plyr-control-spacing * 1.5);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
|
||||
// Offset icon to make the play button look right
|
||||
svg {
|
||||
left: 2px; // Offset to make the play button look right
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--plyr-video-control-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.plyr--playing .plyr__control--overlaid {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.plyr--full-ui.plyr--video .plyr__control--overlaid {
|
||||
display: block;
|
||||
}
|
||||
|
@ -12,11 +12,11 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
min-width: 0; // Fix for Edge issue where content would overflow
|
||||
text-align: center;
|
||||
|
||||
.plyr__progress__container {
|
||||
flex: 1;
|
||||
min-width: 0; // Fix for Edge issue where content would overflow
|
||||
}
|
||||
|
||||
// Spacing
|
||||
@ -41,14 +41,6 @@
|
||||
&.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
|
||||
@ -57,40 +49,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Audio controls
|
||||
.plyr--audio .plyr__controls {
|
||||
background: $plyr-audio-controls-bg;
|
||||
border-radius: inherit;
|
||||
color: $plyr-audio-control-color;
|
||||
padding: $plyr-control-spacing;
|
||||
}
|
||||
|
||||
// Video controls
|
||||
.plyr--video .plyr__controls {
|
||||
background: linear-gradient(rgba($plyr-video-controls-bg, 0), rgba($plyr-video-controls-bg, 0.7));
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
bottom: 0;
|
||||
color: $plyr-video-control-color;
|
||||
left: 0;
|
||||
padding: ($plyr-control-spacing * 2) ($plyr-control-spacing / 2) ($plyr-control-spacing / 2);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
||||
z-index: 3;
|
||||
|
||||
@media (min-width: $plyr-bp-sm) {
|
||||
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing $plyr-control-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide video controls
|
||||
.plyr--video.plyr--hide-controls .plyr__controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
// Some options are hidden by default
|
||||
.plyr [data-plyr='captions'],
|
||||
.plyr [data-plyr='pip'],
|
||||
|
@ -24,7 +24,7 @@
|
||||
// The actual menu container
|
||||
&__container {
|
||||
animation: plyr-popup 0.2s ease;
|
||||
background: $plyr-menu-bg;
|
||||
background: $plyr-menu-background;
|
||||
border-radius: 4px;
|
||||
bottom: 100%;
|
||||
box-shadow: $plyr-menu-shadow;
|
||||
@ -39,14 +39,13 @@
|
||||
|
||||
> div {
|
||||
overflow: hidden;
|
||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
// Arrow
|
||||
&::after {
|
||||
border: 4px solid transparent;
|
||||
border-top-color: $plyr-menu-bg;
|
||||
border-top-color: $plyr-menu-background;
|
||||
content: '';
|
||||
height: 0;
|
||||
position: absolute;
|
||||
@ -74,8 +73,7 @@
|
||||
color: $plyr-menu-color;
|
||||
display: flex;
|
||||
font-size: $plyr-font-size-menu;
|
||||
padding: ceil($plyr-control-padding / 2)
|
||||
ceil($plyr-control-padding * 1.5);
|
||||
padding: ceil($plyr-control-padding / 2) ceil($plyr-control-padding * 1.5);
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
@ -174,7 +172,7 @@
|
||||
|
||||
&[aria-checked='true'] {
|
||||
&::before {
|
||||
background: $plyr-color-main;
|
||||
background: var(--plyr-color-main, $plyr-color-main);
|
||||
}
|
||||
&::after {
|
||||
opacity: 1;
|
||||
|
@ -67,25 +67,16 @@ $plyr-progress-offset: $plyr-range-thumb-height;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr--video .plyr__progress__buffer {
|
||||
box-shadow: 0 1px 1px rgba(#000, 0.15);
|
||||
color: $plyr-video-progress-buffered-bg;
|
||||
}
|
||||
|
||||
.plyr--audio .plyr__progress__buffer {
|
||||
color: $plyr-audio-progress-buffered-bg;
|
||||
}
|
||||
|
||||
// Loading state
|
||||
.plyr--loading .plyr__progress__buffer {
|
||||
animation: plyr-progress 1s linear infinite;
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
$plyr-progress-loading-bg 25%,
|
||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
$plyr-progress-loading-bg 50%,
|
||||
$plyr-progress-loading-bg 75%,
|
||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 50%,
|
||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 75%,
|
||||
transparent 75%,
|
||||
transparent
|
||||
);
|
||||
@ -95,9 +86,9 @@ $plyr-progress-offset: $plyr-range-thumb-height;
|
||||
}
|
||||
|
||||
.plyr--video.plyr--loading .plyr__progress__buffer {
|
||||
background-color: $plyr-video-progress-buffered-bg;
|
||||
background-color: $plyr-video-progress-buffered-background;
|
||||
}
|
||||
|
||||
.plyr--audio.plyr--loading .plyr__progress__buffer {
|
||||
background-color: $plyr-audio-progress-buffered-bg;
|
||||
background-color: $plyr-audio-progress-buffered-background;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
border: 0;
|
||||
border-radius: ($plyr-range-thumb-height * 2);
|
||||
// color is used in JS to populate lower fill for WebKit
|
||||
color: $plyr-range-fill-bg;
|
||||
color: $plyr-range-fill-background;
|
||||
display: block;
|
||||
height: $plyr-range-max-height;
|
||||
margin: 0;
|
||||
@ -19,11 +19,7 @@
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include plyr-range-track();
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
currentColor var(--value, 0%),
|
||||
transparent var(--value, 0%)
|
||||
);
|
||||
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@ -96,69 +92,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video range inputs
|
||||
.plyr--full-ui.plyr--video input[type='range'] {
|
||||
&::-webkit-slider-runnable-track {
|
||||
background-color: $plyr-video-range-track-bg;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
background-color: $plyr-video-range-track-bg;
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
background-color: $plyr-video-range-track-bg;
|
||||
}
|
||||
|
||||
// Pressed styles
|
||||
&:active {
|
||||
&::-webkit-slider-thumb {
|
||||
@include plyr-range-thumb-active();
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include plyr-range-thumb-active();
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include plyr-range-thumb-active();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio range inputs
|
||||
.plyr--full-ui.plyr--audio input[type='range'] {
|
||||
&::-webkit-slider-runnable-track {
|
||||
background-color: $plyr-audio-range-track-bg;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
background-color: $plyr-audio-range-track-bg;
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
background-color: $plyr-audio-range-track-bg;
|
||||
}
|
||||
|
||||
// Pressed styles
|
||||
&:active {
|
||||
&::-webkit-slider-thumb {
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,3 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr--video .plyr__time {
|
||||
text-shadow: 0 1px 1px rgba(#000, 0.15);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__tooltip {
|
||||
background: $plyr-tooltip-bg;
|
||||
background: $plyr-tooltip-background;
|
||||
border-radius: $plyr-tooltip-radius;
|
||||
bottom: 100%;
|
||||
box-shadow: $plyr-tooltip-shadow;
|
||||
@ -27,7 +27,7 @@
|
||||
&::before {
|
||||
border-left: $plyr-tooltip-arrow-size solid transparent;
|
||||
border-right: $plyr-tooltip-arrow-size solid transparent;
|
||||
border-top: $plyr-tooltip-arrow-size solid $plyr-tooltip-bg;
|
||||
border-top: $plyr-tooltip-arrow-size solid $plyr-tooltip-background;
|
||||
bottom: -$plyr-tooltip-arrow-size;
|
||||
content: '';
|
||||
height: 0;
|
||||
|
@ -1,55 +0,0 @@
|
||||
// --------------------------------------------------------------
|
||||
// Video styles
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr--video {
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
|
||||
// Menu open
|
||||
&.plyr--menu-open {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr__video-wrapper {
|
||||
background: #000;
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
// Require z-index to force border-radius
|
||||
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);
|
||||
}
|
||||
}
|
@ -5,33 +5,21 @@
|
||||
.plyr__volume {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
max-width: 110px;
|
||||
min-width: 80px;
|
||||
position: relative;
|
||||
width: 20%;
|
||||
|
||||
input[type='range'] {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
margin-right: ($plyr-control-spacing / 2);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media (min-width: $plyr-bp-sm) {
|
||||
max-width: 90px;
|
||||
}
|
||||
|
||||
@media (min-width: $plyr-bp-md) {
|
||||
max-width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide sound controls on iOS
|
||||
// It's not supported to change volume using JavaScript:
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
// Auto size on iOS as there's no slider
|
||||
.plyr--is-ios .plyr__volume {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Vimeo has no toggle mute method so hide mute button
|
||||
// https://github.com/vimeo/player.js/issues/236#issuecomment-384663183
|
||||
.plyr--is-ios.plyr--vimeo [data-plyr='mute'] {
|
||||
display: none !important;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ $css-vars-use-native: false !default;
|
||||
//
|
||||
// E.G.:
|
||||
// color: var(--main-color);
|
||||
// background: var(--main-bg, green);
|
||||
// background: var(--main-background, green);
|
||||
///
|
||||
@function var($args...) {
|
||||
// CHECK PARAMS
|
||||
|
@ -37,7 +37,7 @@
|
||||
}
|
||||
|
||||
@mixin plyr-range-thumb() {
|
||||
background: $plyr-range-thumb-bg;
|
||||
background: $plyr-range-thumb-background;
|
||||
border: 0;
|
||||
border-radius: 100%;
|
||||
box-shadow: $plyr-range-thumb-shadow;
|
||||
@ -47,7 +47,7 @@
|
||||
width: $plyr-range-thumb-height;
|
||||
}
|
||||
|
||||
@mixin plyr-range-thumb-active($color: rgba($plyr-range-thumb-bg, 0.5)) {
|
||||
@mixin plyr-range-thumb-active($color: rgba($plyr-range-thumb-background, 0.5)) {
|
||||
box-shadow: $plyr-range-thumb-shadow, 0 0 0 $plyr-range-thumb-active-shadow-width $color;
|
||||
}
|
||||
|
||||
@ -66,12 +66,13 @@
|
||||
|
||||
.plyr__video-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: static;
|
||||
}
|
||||
|
||||
// Vimeo requires some different styling
|
||||
&.plyr--vimeo .plyr__video-wrapper {
|
||||
height: 0;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
// The countdown label
|
||||
&::after {
|
||||
background: rgba($plyr-color-gunmetal, 0.8);
|
||||
background: rgba($plyr-color-gray-900, 0.8);
|
||||
border-radius: 2px;
|
||||
bottom: $plyr-control-spacing;
|
||||
color: #fff;
|
||||
|
@ -3,19 +3,19 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
$plyr-preview-padding: $plyr-tooltip-padding !default;
|
||||
$plyr-preview-bg: $plyr-tooltip-bg !default;
|
||||
$plyr-preview-background: $plyr-tooltip-background !default;
|
||||
$plyr-preview-radius: $plyr-tooltip-radius !default;
|
||||
$plyr-preview-shadow: $plyr-tooltip-shadow !default;
|
||||
$plyr-preview-arrow-size: $plyr-tooltip-arrow-size !default;
|
||||
$plyr-preview-image-bg: $plyr-color-heather !default;
|
||||
$plyr-preview-image-background: $plyr-color-gray-200 !default;
|
||||
$plyr-preview-time-font-size: $plyr-font-size-time !default;
|
||||
$plyr-preview-time-padding: 3px 6px !default;
|
||||
$plyr-preview-time-bg: rgba(0, 0, 0, 0.55);
|
||||
$plyr-preview-time-background: rgba(0, 0, 0, 0.55);
|
||||
$plyr-preview-time-color: #fff;
|
||||
$plyr-preview-time-bottom-offset: 6px;
|
||||
|
||||
.plyr__preview-thumb {
|
||||
background-color: $plyr-preview-bg;
|
||||
background-color: $plyr-preview-background;
|
||||
border-radius: 3px;
|
||||
bottom: 100%;
|
||||
box-shadow: $plyr-preview-shadow;
|
||||
@ -38,7 +38,7 @@ $plyr-preview-time-bottom-offset: 6px;
|
||||
&::before {
|
||||
border-left: $plyr-preview-arrow-size solid transparent;
|
||||
border-right: $plyr-preview-arrow-size solid transparent;
|
||||
border-top: $plyr-preview-arrow-size solid $plyr-preview-bg;
|
||||
border-top: $plyr-preview-arrow-size solid $plyr-preview-background;
|
||||
bottom: -$plyr-preview-arrow-size;
|
||||
content: '';
|
||||
height: 0;
|
||||
@ -50,7 +50,7 @@ $plyr-preview-time-bottom-offset: 6px;
|
||||
}
|
||||
|
||||
&__image-container {
|
||||
background: $plyr-preview-image-bg;
|
||||
background: $plyr-preview-image-background;
|
||||
border-radius: ($plyr-preview-radius - 1px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -77,7 +77,7 @@ $plyr-preview-time-bottom-offset: 6px;
|
||||
z-index: 3;
|
||||
|
||||
span {
|
||||
background-color: $plyr-preview-time-bg;
|
||||
background-color: $plyr-preview-time-background;
|
||||
border-radius: ($plyr-preview-radius - 1px);
|
||||
color: $plyr-preview-time-color;
|
||||
font-size: $plyr-preview-time-font-size;
|
@ -37,14 +37,16 @@ $css-vars-use-native: true;
|
||||
@import 'components/poster';
|
||||
@import 'components/times';
|
||||
@import 'components/tooltips';
|
||||
@import 'components/video';
|
||||
@import 'components/progress';
|
||||
@import 'components/volume';
|
||||
|
||||
@import 'types/audio';
|
||||
@import 'types/video';
|
||||
|
||||
@import 'states/fullscreen';
|
||||
|
||||
@import 'plugins/ads';
|
||||
@import 'plugins/previewThumbnails';
|
||||
@import 'plugins/preview-thumbnails';
|
||||
|
||||
@import 'utils/animation';
|
||||
@import 'utils/hidden';
|
||||
|
@ -2,5 +2,5 @@
|
||||
// Badges
|
||||
// ==========================================================================
|
||||
|
||||
$plyr-badge-bg: $plyr-color-fiord !default;
|
||||
$plyr-badge-background: $plyr-color-gray-700 !default;
|
||||
$plyr-badge-color: #fff !default;
|
||||
|
@ -9,10 +9,3 @@ $plyr-font-size-captions-base: $plyr-font-size-base !default;
|
||||
$plyr-font-size-captions-small: $plyr-font-size-small !default;
|
||||
$plyr-font-size-captions-medium: $plyr-font-size-large !default;
|
||||
$plyr-font-size-captions-large: $plyr-font-size-xlarge !default;
|
||||
|
||||
@include css-vars(
|
||||
(
|
||||
--plyr-captions-background: $plyr-captions-background,
|
||||
--plyr-captions-text-color: $plyr-captions-text-color
|
||||
)
|
||||
);
|
||||
|
@ -2,18 +2,32 @@
|
||||
// Colors
|
||||
// ==========================================================================
|
||||
|
||||
$plyr-color-main: #fff !default;
|
||||
$plyr-color-gunmetal: #2f343d !default;
|
||||
$plyr-color-fiord: #4f5b5f !default;
|
||||
$plyr-color-lynch: #6b7d85 !default;
|
||||
$plyr-color-heather: #b7c5cd !default;
|
||||
$plyr-color-main: hsl(198, 100%, 50%) !default;
|
||||
|
||||
// Grayscale
|
||||
$plyr-color-gray-900: hsl(210, 15%, 16%);
|
||||
$plyr-color-gray-800: lighten($plyr-color-gray-900, 9%);
|
||||
$plyr-color-gray-700: lighten($plyr-color-gray-800, 9%);
|
||||
$plyr-color-gray-600: lighten($plyr-color-gray-700, 9%);
|
||||
$plyr-color-gray-500: lighten($plyr-color-gray-600, 9%);
|
||||
$plyr-color-gray-400: lighten($plyr-color-gray-500, 9%);
|
||||
$plyr-color-gray-300: lighten($plyr-color-gray-400, 9%);
|
||||
$plyr-color-gray-200: lighten($plyr-color-gray-300, 9%);
|
||||
$plyr-color-gray-100: lighten($plyr-color-gray-200, 9%);
|
||||
$plyr-color-gray-50: lighten($plyr-color-gray-100, 9%);
|
||||
|
||||
@include css-vars(
|
||||
(
|
||||
--plyr-color-main: $plyr-color-main,
|
||||
--plyr-color-gunmetal: $plyr-color-gunmetal,
|
||||
--plyr-color-fiord: $plyr-color-fiord,
|
||||
--plyr-color-lynch: $plyr-color-lynch,
|
||||
--plyr-color-heather: $plyr-color-heather
|
||||
--plyr-color-gray-900: $plyr-color-gray-900,
|
||||
--plyr-color-gray-800: $plyr-color-gray-800,
|
||||
--plyr-color-gray-700: $plyr-color-gray-700,
|
||||
--plyr-color-gray-600: $plyr-color-gray-600,
|
||||
--plyr-color-gray-500: $plyr-color-gray-500,
|
||||
--plyr-color-gray-400: $plyr-color-gray-400,
|
||||
--plyr-color-gray-300: $plyr-color-gray-300,
|
||||
--plyr-color-gray-200: $plyr-color-gray-200,
|
||||
--plyr-color-gray-100: $plyr-color-gray-100,
|
||||
--plyr-color-gray-50: $plyr-color-gray-50
|
||||
)
|
||||
);
|
||||
|
@ -7,29 +7,12 @@ $plyr-control-spacing: 10px !default;
|
||||
$plyr-control-padding: ($plyr-control-spacing * 0.7) !default;
|
||||
$plyr-control-radius: 3px !default;
|
||||
|
||||
$plyr-video-controls-bg: #000 !default;
|
||||
$plyr-video-controls-background: linear-gradient(rgba(#000, 0), rgba(#000, 0.75)) !default;
|
||||
$plyr-video-control-color: #fff !default;
|
||||
$plyr-video-control-color-hover: #000 !default;
|
||||
$plyr-video-control-bg-hover: var(--plyr-color-main) !default;
|
||||
$plyr-video-control-color-hover: #fff !default;
|
||||
$plyr-video-control-background-hover: $plyr-color-main !default;
|
||||
|
||||
$plyr-audio-controls-bg: #fff !default;
|
||||
$plyr-audio-control-color: var(--plyr-color-fiord) !default;
|
||||
$plyr-audio-controls-background: #fff !default;
|
||||
$plyr-audio-control-color: $plyr-color-gray-700 !default;
|
||||
$plyr-audio-control-color-hover: #fff !default;
|
||||
$plyr-audio-control-bg-hover: var(--plyr-color-main) !default;
|
||||
|
||||
@include css-vars(
|
||||
(
|
||||
--plyr-control-icon-size: $plyr-control-icon-size,
|
||||
--plyr-control-spacing: $plyr-control-spacing,
|
||||
--plyr-control-padding: $plyr-control-padding,
|
||||
--plyr-control-radius: $plyr-control-radius,
|
||||
--plyr-video-controls-bg: $plyr-video-controls-bg,
|
||||
--plyr-video-control-color: $plyr-video-control-color,
|
||||
--plyr-video-control-color-hover: $plyr-video-control-color-hover,
|
||||
--plyr-video-control-bg-hover: $plyr-video-control-bg-hover,
|
||||
--plyr-audio-controls-bg: $plyr-audio-controls-bg,
|
||||
--plyr-audio-control-color: $plyr-audio-control-color,
|
||||
--plyr-audio-control-color-hover: $plyr-audio-control-color-hover,
|
||||
--plyr-audio-control-bg-hover: $plyr-audio-control-bg-hover
|
||||
)
|
||||
);
|
||||
$plyr-audio-control-background-hover: $plyr-color-main !default;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user