Merge pull request #1472 from sampotts/develop

v3.5.5
This commit is contained in:
Sam Potts 2019-06-21 00:12:10 +10:00 committed by GitHub
commit 95092edc93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 17147 additions and 5472 deletions

View File

@ -11,24 +11,7 @@
"jQuery": false
},
"rules": {
"import/no-cycle": 1,
"no-const-assign": 1,
"no-shadow": 0,
"no-this-before-super": 1,
"no-undef": 1,
"no-unreachable": 1,
"no-unused-vars": 1,
"constructor-super": 1,
"valid-typeof": 1,
"indent": [2, 4, { "SwitchCase": 1 }],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"eqeqeq": [2, "always"],
"one-var": [2, "never"],
"comma-dangle": [2, "always-multiline"],
"spaced-comment": [2, "always"],
"no-restricted-globals": 2,
"no-param-reassign": [2, { "props": false }],
"import/no-cycle": "warn",
"padding-line-between-statements": [
"error",
{

2
.gitignore vendored
View File

@ -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/

View File

@ -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": {

View File

@ -1,3 +1,10 @@
## 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

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

13424
demo/dist/demo.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
demo/dist/error.css vendored

File diff suppressed because one or more lines are too long

View File

@ -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"
@ -50,12 +50,23 @@
type="font/woff2"
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
/>
<!-- Google Analytics-->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-132699580-1');
</script>
</head>
<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">
@ -106,22 +117,20 @@
</p>
<div class="call-to-action">
<span class="button--with-count">
<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
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>
<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>
</div>
</header>
@ -256,23 +265,13 @@
<a
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&amp;url=http%3A%2F%2Fplyr.io&amp;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
View File

@ -0,0 +1,14 @@
{
"name": "plyr-demo",
"version": "1.0.0",
"description": "Demo for Plyr",
"homepage": "https://plyr.io",
"author": "Sam Potts <sam@potts.es>",
"dependencies": {
"core-js": "^3.1.3",
"custom-event-polyfill": "^1.0.7",
"raven-js": "^3.27.1",
"shr-buttons": "2.0.2",
"url-polyfill": "^1.1.5"
}
}

View File

@ -4,8 +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;
@ -17,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
@ -93,131 +71,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 &ndash; &ldquo;It All Began With A Burst&rdquo;',
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));
@ -226,9 +85,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
@ -236,7 +117,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}`);
@ -246,36 +127,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
View 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 &ndash; &ldquo;It All Began With A Burst&rdquo;',
sources: [
{
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
type: 'audio/mp3',
},
{
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
type: 'audio/ogg',
},
],
},
youtube: {
type: 'video',
sources: [
{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube',
},
],
},
vimeo: {
type: 'video',
sources: [
{
src: 'https://vimeo.com/76979871',
provider: 'vimeo',
},
],
},
};
export default sources;

31
demo/src/js/tab-focus.js Normal file
View 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);
});

View 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;

View File

@ -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 {
@ -38,7 +38,6 @@
&:hover {
box-shadow: 0 2px 2px rgba(#000, 0.1);
transform: translateY(-1px);
}
&:focus {
@ -50,7 +49,7 @@
}
&:active {
transform: translateY(1px);
top: 1px;
}
}
@ -66,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;

View File

@ -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);
}
}
}

View File

@ -19,5 +19,5 @@ label svg {
a .icon,
.btn .icon {
margin-right: floor($spacing-base / 3);
margin-right: ($spacing-base / 2);
}

View File

@ -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;

View File

@ -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-5;
.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;
}

View File

@ -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;

View File

@ -11,3 +11,17 @@
opacity: 1;
}
}
@keyframes shrinkHide {
0% {
opacity: 0.5;
width: 38px;
}
20% {
width: 45px;
}
100% {
opacity: 0;
width: 0;
}
}

View File

@ -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);
}

View File

@ -2,31 +2,41 @@
// Colors
// ==========================================================================
// Greyscale
$gray-dark: #343f4a;
$gray: #55646b;
$gray-light: #cbd0d3;
$gray-lighter: #dbe3e8;
$off-white: #f2f5f7;
// Grayscale
$color-gray-9: hsl(210, 15%, 16%);
$color-gray-8: lighten($color-gray-9, 9%);
$color-gray-7: lighten($color-gray-8, 9%);
$color-gray-6: lighten($color-gray-7, 9%);
$color-gray-5: lighten($color-gray-6, 9%);
$color-gray-4: lighten($color-gray-5, 9%);
$color-gray-3: lighten($color-gray-4, 9%);
$color-gray-2: lighten($color-gray-3, 9%);
$color-gray-1: lighten($color-gray-2, 9%);
$color-gray-0: lighten($color-gray-1, 9%);
// Branding
$color-brand-primary: hsl(198, 100%, 50%);
// Text
$color-text: #fff;
// Plyr
$color-brand-primary: #1aafff;
$color-text: $color-gray-7;
$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-6;
// Focus
$tab-focus-default-color: #fff;

View File

@ -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);

View File

@ -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;

View File

@ -2,4 +2,4 @@
// Colors
// ==========================================================================
$spacing-base: 20px;
$spacing-base: 16px;

View File

@ -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 {

View File

@ -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
View File

@ -0,0 +1,28 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
core-js@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.3.tgz#95700bca5f248f5f78c0ec63e784eca663ec4138"
integrity sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==
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.1:
version "3.27.1"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.27.1.tgz#e187a12982061908ccbf935af0640c9043d7d666"
integrity sha512-r/9CwSbaGfBFjo4hGR45DAmrukUKkQ4HdMu80PlVLDY1t8f9b4aaZzTsFegaafu7EGhEYougWDJ9/IcTdYdLXQ==
shr-buttons@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/shr-buttons/-/shr-buttons-2.0.2.tgz#a3c11b36067b8902afbff9845d0e023f9f76865f"
integrity sha512-UYaZjF5FK1NZrXjQF8YkWOzxM7Z210lv3TDsFMTTp7hbJrdybNPF4WX4k5a+dW25R97FMBZaSnMWvI9TpuCCoA==
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

File diff suppressed because one or more lines are too long

330
dist/plyr.js vendored
View File

@ -540,6 +540,49 @@ typeof navigator === "object" && (function (global, factory) {
empty: isEmpty$1
};
// ==========================================================================
var transitionEndEvent = function () {
var element = document.createElement('span');
var events = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var type = Object.keys(events).find(function (event) {
return element.style[event] !== undefined;
});
return is$1.string(type) ? events[type] : false;
}(); // Force repaint of element
function repaint(element, delay) {
setTimeout(function () {
try {
// 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
}
}, delay);
}
// ==========================================================================
// Browser sniffing
// Unfortunately, due to mixed support, UA sniffing is required
// ==========================================================================
var browser = {
isIE:
/* @cc_on!@ */
!!document.documentMode,
isEdge: window.navigator.userAgent.includes('Edge'),
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
};
// ==========================================================================
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
// https://www.youtube.com/watch?v=NPM6172J22g
@ -903,13 +946,10 @@ typeof navigator === "object" && (function (global, factory) {
if (!is$1.boolean(hide)) {
hide = !element.hidden;
}
} // eslint-disable-next-line no-param-reassign
if (hide) {
element.setAttribute('hidden', '');
} else {
element.removeAttribute('hidden');
}
element.hidden = hide;
} // Mirror Element.classList.toggle, with IE compatibility for "force" argument
function toggleClass(element, className, force) {
@ -943,8 +983,8 @@ typeof navigator === "object" && (function (global, factory) {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
var matches = match;
return matches.call(element, selector);
var method = match;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -1008,47 +1048,6 @@ typeof navigator === "object" && (function (global, factory) {
}
}
// ==========================================================================
var transitionEndEvent = function () {
var element = document.createElement('span');
var events = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var type = Object.keys(events).find(function (event) {
return element.style[event] !== undefined;
});
return is$1.string(type) ? events[type] : false;
}(); // Force repaint of element
function repaint(element) {
setTimeout(function () {
try {
toggleHidden(element, true);
element.offsetHeight; // eslint-disable-line
toggleHidden(element, false);
} catch (e) {// Do nothing
}
}, 0);
}
// ==========================================================================
// Browser sniffing
// Unfortunately, due to mixed support, UA sniffing is required
// ==========================================================================
var browser = {
isIE:
/* @cc_on!@ */
!!document.documentMode,
isEdge: window.navigator.userAgent.includes('Edge'),
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
};
var defaultCodecs = {
'audio/ogg': 'vorbis',
'audio/wav': '1',
@ -1172,12 +1171,8 @@ typeof navigator === "object" && (function (global, factory) {
}
function getAspectRatio(input) {
var parse = function parse(ratio) {
if (!validateRatio(ratio)) {
return null;
}
return ratio.split(':').map(Number);
}; // Provided ratio
return validateRatio(ratio) ? ratio.split(':').map(Number) : null;
}; // Try provided ratio
var ratio = parse(input); // Get from config
@ -1264,16 +1259,19 @@ typeof navigator === "object" && (function (global, factory) {
return;
}
var player = this; // Set aspect ratio if set
var player = this; // Set aspect ratio if fixed
if (!is$1.empty(this.config.ratio)) {
setAspectRatio.call(player);
} // Quality
setAspectRatio.call(player); // Quality
Object.defineProperty(player.media, 'quality', {
get: function get() {
// Get sources
var sources = html5.getSources.call(player);
var source = sources.find(function (source) {
return source.getAttribute('src') === player.source;
var source = sources.find(function (s) {
return s.getAttribute('src') === player.source;
}); // Return size, if match is found
return source && Number(source.getAttribute('size'));
@ -1282,8 +1280,8 @@ typeof navigator === "object" && (function (global, factory) {
// Get sources
var sources = html5.getSources.call(player); // Get first match for requested size
var source = sources.find(function (source) {
return Number(source.getAttribute('size')) === input;
var source = sources.find(function (s) {
return Number(s.getAttribute('size')) === input;
}); // No matching source found
if (!source) {
@ -1473,10 +1471,10 @@ typeof navigator === "object" && (function (global, factory) {
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
k = _ref2[0],
v = _ref2[1];
string = replaceAll(string, key, value);
string = replaceAll(string, k, v);
});
return string;
}
@ -1611,6 +1609,7 @@ typeof navigator === "object" && (function (global, factory) {
};
var update = function update(container, data) {
// eslint-disable-next-line no-param-reassign
container.innerHTML = data; // Check again incase of race condition
if (hasId && exists()) {
@ -2085,9 +2084,9 @@ typeof navigator === "object" && (function (global, factory) {
get: function get() {
return menuItem.getAttribute('aria-checked') === 'true';
},
set: function set(checked) {
set: function set(check) {
// Ensure exclusivity
if (checked) {
if (check) {
Array.from(menuItem.parentNode.children).filter(function (node) {
return matches$1(node, '[role="menuitemradio"]');
}).forEach(function (node) {
@ -2095,7 +2094,7 @@ typeof navigator === "object" && (function (global, factory) {
});
}
menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
}
});
this.listeners.bind(menuItem, 'click keyup', function (event) {
@ -2197,16 +2196,16 @@ typeof navigator === "object" && (function (global, factory) {
var value = 0;
var setProgress = function setProgress(target, input) {
var value = is$1.number(input) ? input : 0;
var val = is$1.number(input) ? input : 0;
var progress = is$1.element(target) ? target : _this4.elements.display.buffer; // Update value and label
if (is$1.element(progress)) {
progress.value = value; // Update text label inside
progress.value = val; // Update text label inside
var label = progress.getElementsByTagName('span')[0];
if (is$1.element(label)) {
label.childNodes[0].nodeValue = value;
label.childNodes[0].nodeValue = val;
}
}
};
@ -2275,15 +2274,12 @@ typeof navigator === "object" && (function (global, factory) {
// Bail if setting not true
if (!this.config.tooltips.seek || !is$1.element(this.elements.inputs.seek) || !is$1.element(this.elements.display.seekTooltip) || this.duration === 0) {
return;
} // Calculate percentage
}
var percent = 0;
var clientRect = this.elements.progress.getBoundingClientRect();
var visible = "".concat(this.config.classNames.tooltip, "--visible");
var toggle = function toggle(_toggle) {
toggleClass(_this5.elements.display.seekTooltip, visible, _toggle);
var toggle = function toggle(show) {
return toggleClass(_this5.elements.display.seekTooltip, visible, show);
}; // Hide on touch
@ -2293,6 +2289,9 @@ typeof navigator === "object" && (function (global, factory) {
} // Determine percentage, if already visible
var percent = 0;
var clientRect = this.elements.progress.getBoundingClientRect();
if (is$1.event(event)) {
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
@ -2653,8 +2652,8 @@ typeof navigator === "object" && (function (global, factory) {
var target = pane;
if (!is$1.element(target)) {
target = Object.values(this.elements.settings.panels).find(function (pane) {
return !pane.hidden;
target = Object.values(this.elements.settings.panels).find(function (p) {
return !p.hidden;
});
}
@ -2909,17 +2908,15 @@ typeof navigator === "object" && (function (global, factory) {
if (control === 'settings' && !is$1.empty(_this10.config.settings)) {
var _control = createElement('div', extend({}, defaultAttributes, {
var wrapper = createElement('div', extend({}, defaultAttributes, {
class: "".concat(defaultAttributes.class, " plyr__menu").trim(),
hidden: ''
}));
_control.appendChild(createButton.call(_this10, 'settings', {
wrapper.appendChild(createButton.call(_this10, 'settings', {
'aria-haspopup': true,
'aria-controls': "plyr-settings-".concat(data.id),
'aria-expanded': false
}));
var popup = createElement('div', {
class: 'plyr__menu__container',
id: "plyr-settings-".concat(data.id),
@ -3008,12 +3005,10 @@ typeof navigator === "object" && (function (global, factory) {
});
popup.appendChild(inner);
_control.appendChild(popup);
container.appendChild(_control);
wrapper.appendChild(popup);
container.appendChild(wrapper);
_this10.elements.settings.popup = popup;
_this10.elements.settings.menu = _control;
_this10.elements.settings.menu = wrapper;
} // Picture in picture button
@ -3344,6 +3339,7 @@ typeof navigator === "object" && (function (global, factory) {
meta.set(track, {
default: track.mode === 'showing'
}); // Turn off native caption rendering to avoid double captions
// eslint-disable-next-line no-param-reassign
track.mode = 'hidden'; // Add event listener for cue changes
@ -3525,8 +3521,8 @@ typeof navigator === "object" && (function (global, factory) {
});
var track;
languages.every(function (language) {
track = sorted.find(function (track) {
return track.language === language;
track = sorted.find(function (t) {
return t.language === language;
});
return !track; // Break iteration if there is a match
}); // If no match is found but is required, get first
@ -3705,7 +3701,8 @@ typeof navigator === "object" && (function (global, factory) {
controls: ['play-large', // 'restart',
// 'rewind',
'play', // 'fast-forward',
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'progress', 'current-time', // 'duration',
'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'fullscreen'],
settings: ['captions', 'quality', 'speed'],
// Localisation
@ -3763,8 +3760,7 @@ typeof navigator === "object" && (function (global, factory) {
},
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'
@ -4049,8 +4045,6 @@ typeof navigator === "object" && (function (global, factory) {
}
function toggleFallback() {
var _this = this;
var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Store or restore scroll position
@ -4090,12 +4084,7 @@ typeof navigator === "object" && (function (global, factory) {
viewport.content = viewport.content.split(',').filter(function (part) {
return part.trim() !== property;
}).join(',');
} // Force a repaint as sometimes Safari doesn't want to fill the screen
setTimeout(function () {
return repaint(_this.target);
}, 100);
}
} // Toggle button and fire events
@ -4106,7 +4095,7 @@ typeof navigator === "object" && (function (global, factory) {
/*#__PURE__*/
function () {
function Fullscreen(player) {
var _this2 = this;
var _this = this;
_classCallCheck(this, Fullscreen);
@ -4126,16 +4115,16 @@ typeof navigator === "object" && (function (global, factory) {
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
// TODO: Filter for target??
onChange.call(_this2);
onChange.call(_this);
}); // Fullscreen toggle on double click
on.call(this.player, this.player.elements.container, 'dblclick', function (event) {
// Ignore double click in controls
if (is$1.element(_this2.player.elements.controls) && _this2.player.elements.controls.contains(event.target)) {
if (is$1.element(_this.player.elements.controls) && _this.player.elements.controls.contains(event.target)) {
return;
}
_this2.toggle();
_this.toggle();
}); // Update the UI
this.update();
@ -4483,7 +4472,9 @@ typeof navigator === "object" && (function (global, factory) {
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); // Set state
Array.from(this.elements.buttons.play || []).forEach(function (target) {
target.pressed = _this3.playing;
Object.assign(target, {
pressed: _this3.playing
});
}); // Only update controls on non timeupdate events
if (is$1.event(event) && event.type === 'timeupdate') {
@ -4510,13 +4501,13 @@ typeof navigator === "object" && (function (global, factory) {
},
// Toggle controls based on state and `force` argument
toggleControls: function toggleControls(force) {
var controls = this.elements.controls;
var controlsElement = this.elements.controls;
if (controls && this.config.hideControls) {
if (controlsElement && this.config.hideControls) {
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
var recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek));
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
}
}
};
@ -4666,14 +4657,14 @@ typeof navigator === "object" && (function (global, factory) {
break;
/* case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
default:
break;
@ -4784,11 +4775,11 @@ typeof navigator === "object" && (function (global, factory) {
on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) {
var controls = elements.controls; // Remove button states for fullscreen
var controlsElement = elements.controls; // Remove button states for fullscreen
if (controls && event.type === 'enterfullscreen') {
controls.pressed = false;
controls.hover = false;
if (controlsElement && event.type === 'enterfullscreen') {
controlsElement.pressed = false;
controlsElement.hover = false;
} // Show, then hide after a timeout unless another control event occurs
@ -4807,15 +4798,7 @@ typeof navigator === "object" && (function (global, factory) {
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}); // Force edge to repaint on exit fullscreen
// TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen
/* if (browser.isEdge) {
on.call(player, elements.container, 'exitfullscreen', () => {
setTimeout(() => repaint(elements.container), 100);
});
} */
// Set a gutter for Vimeo
}); // Set a gutter for Vimeo
var setGutter = function setGutter(ratio, padding, toggle) {
if (!player.isVimeo) {
@ -4850,16 +4833,21 @@ typeof navigator === "object" && (function (global, factory) {
};
var resized = function resized() {
window.clearTimeout(timers.resized);
timers.resized = window.setTimeout(setPlayerSize, 50);
clearTimeout(timers.resized);
timers.resized = setTimeout(setPlayerSize, 50);
};
on.call(player, elements.container, 'enterfullscreen exitfullscreen', function (event) {
var _player$fullscreen = player.fullscreen,
target = _player$fullscreen.target,
usingNative = _player$fullscreen.usingNative; // Ignore for iOS native
usingNative = _player$fullscreen.usingNative; // Ignore events not from target
if (!player.isEmbed || target !== elements.container) {
if (target !== elements.container) {
return;
} // If it's not an embed and no ratio specified
if (!player.isEmbed && is$1.empty(player.config.ratio)) {
return;
}
@ -5253,7 +5241,6 @@ typeof navigator === "object" && (function (global, factory) {
this.bind(elements.controls, 'focusin', function () {
var config = player.config,
elements = player.elements,
timers = player.timers; // Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle
@ -5995,23 +5982,22 @@ typeof navigator === "object" && (function (global, factory) {
if (is$1.object(window.YT) && is$1.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Load the API
loadScript(this.config.urls.youtube.sdk).catch(function (error) {
_this.debug.warn('YouTube API failed to load', error);
}); // Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; // Add to queue
window.onYouTubeReadyCallbacks.push(function () {
youtube.ready.call(_this);
}); // Set callback to process queue
// Reference current global callback
var callback = window.onYouTubeIframeAPIReady; // Set callback to process queue
window.onYouTubeIframeAPIReady = function () {
window.onYouTubeReadyCallbacks.forEach(function (callback) {
// Call global callback if set
if (is$1.function(callback)) {
callback();
});
};
}
youtube.ready.call(_this);
}; // Load the SDK
loadScript(this.config.urls.youtube.sdk).catch(function (error) {
_this.debug.warn('YouTube API failed to load', error);
});
}
},
// Get the media title
@ -6041,7 +6027,7 @@ typeof navigator === "object" && (function (global, factory) {
ready: function ready() {
var player = this; // Ignore already setup (race condition)
var currentId = player.media.getAttribute('id');
var currentId = player.media && player.media.getAttribute('id');
if (!is$1.empty(currentId) && currentId.startsWith('youtube-')) {
return;
@ -6066,8 +6052,8 @@ typeof navigator === "object" && (function (global, factory) {
});
player.media = replaceElement(container, player.media); // Id to poster wrapper
var posterSrc = function posterSrc(format) {
return "https://i.ytimg.com/vi/".concat(videoId, "/").concat(format, "default.jpg");
var posterSrc = function posterSrc(s) {
return "https://i.ytimg.com/vi/".concat(videoId, "/").concat(s, "default.jpg");
}; // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
@ -6080,9 +6066,9 @@ typeof navigator === "object" && (function (global, factory) {
}) // 360p padded 4:3. Always exists
.then(function (image) {
return ui.setPoster.call(player, image.src);
}).then(function (posterSrc) {
}).then(function (src) {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!posterSrc.includes('maxres')) {
if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
}).catch(function () {});
@ -6623,8 +6609,8 @@ typeof navigator === "object" && (function (global, factory) {
}); // Advertisement regular events
Object.keys(google.ima.AdEvent.Type).forEach(function (type) {
_this6.manager.addEventListener(google.ima.AdEvent.Type[type], function (event) {
return _this6.onAdEvent(event);
_this6.manager.addEventListener(google.ima.AdEvent.Type[type], function (e) {
return _this6.onAdEvent(e);
});
}); // Resolve our adsManager
@ -6672,8 +6658,7 @@ typeof navigator === "object" && (function (global, factory) {
var adData = event.getAdData(); // Proxy event
var dispatchEvent = function dispatchEvent(type) {
var event = "ads".concat(type.replace(/_/g, '').toLowerCase());
triggerEvent.call(_this8.player, _this8.player.media, event);
triggerEvent.call(_this8.player, _this8.player.media, "ads".concat(type.replace(/_/g, '').toLowerCase()));
}; // Bubble the event
@ -7129,7 +7114,11 @@ typeof navigator === "object" && (function (global, factory) {
}
this.getThumbnails().then(function () {
// Render DOM elements
if (!_this.enabled) {
return;
} // Render DOM elements
_this.render(); // Check to see if thumb container size was specified manually in CSS
@ -7464,6 +7453,7 @@ typeof navigator === "object" && (function (global, factory) {
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
// eslint-disable-next-line no-param-reassign
image.dataset.deleting = true; // This has to be set before the timeout - to prevent issues switching between hover and scrub
var currentImageContainer = _this8.currentImageContainer;
@ -7642,10 +7632,14 @@ typeof navigator === "object" && (function (global, factory) {
} // Find difference between height and preview container height
var multiplier = this.thumbContainerHeight / frame.h;
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px");
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px");
previewImage.style.left = "-".concat(frame.x * multiplier, "px");
var multiplier = this.thumbContainerHeight / frame.h; // eslint-disable-next-line no-param-reassign
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.left = "-".concat(frame.x * multiplier, "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.top = "-".concat(frame.y * multiplier, "px");
}
}, {
@ -8477,32 +8471,32 @@ typeof navigator === "object" && (function (global, factory) {
}, {
key: "isHTML5",
get: function get() {
return Boolean(this.provider === providers.html5);
return this.provider === providers.html5;
}
}, {
key: "isEmbed",
get: function get() {
return Boolean(this.isYouTube || this.isVimeo);
return this.isYouTube || this.isVimeo;
}
}, {
key: "isYouTube",
get: function get() {
return Boolean(this.provider === providers.youtube);
return this.provider === providers.youtube;
}
}, {
key: "isVimeo",
get: function get() {
return Boolean(this.provider === providers.vimeo);
return this.provider === providers.vimeo;
}
}, {
key: "isVideo",
get: function get() {
return Boolean(this.type === types.video);
return this.type === types.video;
}
}, {
key: "isAudio",
get: function get() {
return Boolean(this.type === types.audio);
return this.type === types.audio;
}
}, {
key: "playing",

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/plyr.min.mjs vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

330
dist/plyr.mjs vendored
View File

@ -534,6 +534,49 @@ var is$1 = {
empty: isEmpty$1
};
// ==========================================================================
var transitionEndEvent = function () {
var element = document.createElement('span');
var events = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var type = Object.keys(events).find(function (event) {
return element.style[event] !== undefined;
});
return is$1.string(type) ? events[type] : false;
}(); // Force repaint of element
function repaint(element, delay) {
setTimeout(function () {
try {
// 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
}
}, delay);
}
// ==========================================================================
// Browser sniffing
// Unfortunately, due to mixed support, UA sniffing is required
// ==========================================================================
var browser = {
isIE:
/* @cc_on!@ */
!!document.documentMode,
isEdge: window.navigator.userAgent.includes('Edge'),
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
};
// ==========================================================================
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
// https://www.youtube.com/watch?v=NPM6172J22g
@ -897,13 +940,10 @@ function toggleHidden(element, hidden) {
if (!is$1.boolean(hide)) {
hide = !element.hidden;
}
} // eslint-disable-next-line no-param-reassign
if (hide) {
element.setAttribute('hidden', '');
} else {
element.removeAttribute('hidden');
}
element.hidden = hide;
} // Mirror Element.classList.toggle, with IE compatibility for "force" argument
function toggleClass(element, className, force) {
@ -937,8 +977,8 @@ function matches$1(element, selector) {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
var matches = match;
return matches.call(element, selector);
var method = match;
return method.call(element, selector);
} // Find all elements
function getElements(selector) {
@ -1002,47 +1042,6 @@ function setFocus() {
}
}
// ==========================================================================
var transitionEndEvent = function () {
var element = document.createElement('span');
var events = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var type = Object.keys(events).find(function (event) {
return element.style[event] !== undefined;
});
return is$1.string(type) ? events[type] : false;
}(); // Force repaint of element
function repaint(element) {
setTimeout(function () {
try {
toggleHidden(element, true);
element.offsetHeight; // eslint-disable-line
toggleHidden(element, false);
} catch (e) {// Do nothing
}
}, 0);
}
// ==========================================================================
// Browser sniffing
// Unfortunately, due to mixed support, UA sniffing is required
// ==========================================================================
var browser = {
isIE:
/* @cc_on!@ */
!!document.documentMode,
isEdge: window.navigator.userAgent.includes('Edge'),
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
};
var defaultCodecs = {
'audio/ogg': 'vorbis',
'audio/wav': '1',
@ -1166,12 +1165,8 @@ function reduceAspectRatio(ratio) {
}
function getAspectRatio(input) {
var parse = function parse(ratio) {
if (!validateRatio(ratio)) {
return null;
}
return ratio.split(':').map(Number);
}; // Provided ratio
return validateRatio(ratio) ? ratio.split(':').map(Number) : null;
}; // Try provided ratio
var ratio = parse(input); // Get from config
@ -1258,16 +1253,19 @@ var html5 = {
return;
}
var player = this; // Set aspect ratio if set
var player = this; // Set aspect ratio if fixed
if (!is$1.empty(this.config.ratio)) {
setAspectRatio.call(player);
} // Quality
setAspectRatio.call(player); // Quality
Object.defineProperty(player.media, 'quality', {
get: function get() {
// Get sources
var sources = html5.getSources.call(player);
var source = sources.find(function (source) {
return source.getAttribute('src') === player.source;
var source = sources.find(function (s) {
return s.getAttribute('src') === player.source;
}); // Return size, if match is found
return source && Number(source.getAttribute('size'));
@ -1276,8 +1274,8 @@ var html5 = {
// Get sources
var sources = html5.getSources.call(player); // Get first match for requested size
var source = sources.find(function (source) {
return Number(source.getAttribute('size')) === input;
var source = sources.find(function (s) {
return Number(s.getAttribute('size')) === input;
}); // No matching source found
if (!source) {
@ -1467,10 +1465,10 @@ var i18n = {
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
k = _ref2[0],
v = _ref2[1];
string = replaceAll(string, key, value);
string = replaceAll(string, k, v);
});
return string;
}
@ -1605,6 +1603,7 @@ function loadSprite(url, id) {
};
var update = function update(container, data) {
// eslint-disable-next-line no-param-reassign
container.innerHTML = data; // Check again incase of race condition
if (hasId && exists()) {
@ -2079,9 +2078,9 @@ var controls = {
get: function get() {
return menuItem.getAttribute('aria-checked') === 'true';
},
set: function set(checked) {
set: function set(check) {
// Ensure exclusivity
if (checked) {
if (check) {
Array.from(menuItem.parentNode.children).filter(function (node) {
return matches$1(node, '[role="menuitemradio"]');
}).forEach(function (node) {
@ -2089,7 +2088,7 @@ var controls = {
});
}
menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
}
});
this.listeners.bind(menuItem, 'click keyup', function (event) {
@ -2191,16 +2190,16 @@ var controls = {
var value = 0;
var setProgress = function setProgress(target, input) {
var value = is$1.number(input) ? input : 0;
var val = is$1.number(input) ? input : 0;
var progress = is$1.element(target) ? target : _this4.elements.display.buffer; // Update value and label
if (is$1.element(progress)) {
progress.value = value; // Update text label inside
progress.value = val; // Update text label inside
var label = progress.getElementsByTagName('span')[0];
if (is$1.element(label)) {
label.childNodes[0].nodeValue = value;
label.childNodes[0].nodeValue = val;
}
}
};
@ -2269,15 +2268,12 @@ var controls = {
// Bail if setting not true
if (!this.config.tooltips.seek || !is$1.element(this.elements.inputs.seek) || !is$1.element(this.elements.display.seekTooltip) || this.duration === 0) {
return;
} // Calculate percentage
}
var percent = 0;
var clientRect = this.elements.progress.getBoundingClientRect();
var visible = "".concat(this.config.classNames.tooltip, "--visible");
var toggle = function toggle(_toggle) {
toggleClass(_this5.elements.display.seekTooltip, visible, _toggle);
var toggle = function toggle(show) {
return toggleClass(_this5.elements.display.seekTooltip, visible, show);
}; // Hide on touch
@ -2287,6 +2283,9 @@ var controls = {
} // Determine percentage, if already visible
var percent = 0;
var clientRect = this.elements.progress.getBoundingClientRect();
if (is$1.event(event)) {
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
@ -2647,8 +2646,8 @@ var controls = {
var target = pane;
if (!is$1.element(target)) {
target = Object.values(this.elements.settings.panels).find(function (pane) {
return !pane.hidden;
target = Object.values(this.elements.settings.panels).find(function (p) {
return !p.hidden;
});
}
@ -2903,17 +2902,15 @@ var controls = {
if (control === 'settings' && !is$1.empty(_this10.config.settings)) {
var _control = createElement('div', extend({}, defaultAttributes, {
var wrapper = createElement('div', extend({}, defaultAttributes, {
class: "".concat(defaultAttributes.class, " plyr__menu").trim(),
hidden: ''
}));
_control.appendChild(createButton.call(_this10, 'settings', {
wrapper.appendChild(createButton.call(_this10, 'settings', {
'aria-haspopup': true,
'aria-controls': "plyr-settings-".concat(data.id),
'aria-expanded': false
}));
var popup = createElement('div', {
class: 'plyr__menu__container',
id: "plyr-settings-".concat(data.id),
@ -3002,12 +2999,10 @@ var controls = {
});
popup.appendChild(inner);
_control.appendChild(popup);
container.appendChild(_control);
wrapper.appendChild(popup);
container.appendChild(wrapper);
_this10.elements.settings.popup = popup;
_this10.elements.settings.menu = _control;
_this10.elements.settings.menu = wrapper;
} // Picture in picture button
@ -3338,6 +3333,7 @@ var captions = {
meta.set(track, {
default: track.mode === 'showing'
}); // Turn off native caption rendering to avoid double captions
// eslint-disable-next-line no-param-reassign
track.mode = 'hidden'; // Add event listener for cue changes
@ -3519,8 +3515,8 @@ var captions = {
});
var track;
languages.every(function (language) {
track = sorted.find(function (track) {
return track.language === language;
track = sorted.find(function (t) {
return t.language === language;
});
return !track; // Break iteration if there is a match
}); // If no match is found but is required, get first
@ -3699,7 +3695,8 @@ var defaults$1 = {
controls: ['play-large', // 'restart',
// 'rewind',
'play', // 'fast-forward',
'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'progress', 'current-time', // 'duration',
'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
'fullscreen'],
settings: ['captions', 'quality', 'speed'],
// Localisation
@ -3757,8 +3754,7 @@ var defaults$1 = {
},
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'
@ -4043,8 +4039,6 @@ function onChange() {
}
function toggleFallback() {
var _this = this;
var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Store or restore scroll position
@ -4084,12 +4078,7 @@ function toggleFallback() {
viewport.content = viewport.content.split(',').filter(function (part) {
return part.trim() !== property;
}).join(',');
} // Force a repaint as sometimes Safari doesn't want to fill the screen
setTimeout(function () {
return repaint(_this.target);
}, 100);
}
} // Toggle button and fire events
@ -4100,7 +4089,7 @@ var Fullscreen =
/*#__PURE__*/
function () {
function Fullscreen(player) {
var _this2 = this;
var _this = this;
_classCallCheck(this, Fullscreen);
@ -4120,16 +4109,16 @@ function () {
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
// TODO: Filter for target??
onChange.call(_this2);
onChange.call(_this);
}); // Fullscreen toggle on double click
on.call(this.player, this.player.elements.container, 'dblclick', function (event) {
// Ignore double click in controls
if (is$1.element(_this2.player.elements.controls) && _this2.player.elements.controls.contains(event.target)) {
if (is$1.element(_this.player.elements.controls) && _this.player.elements.controls.contains(event.target)) {
return;
}
_this2.toggle();
_this.toggle();
}); // Update the UI
this.update();
@ -4477,7 +4466,9 @@ var ui = {
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); // Set state
Array.from(this.elements.buttons.play || []).forEach(function (target) {
target.pressed = _this3.playing;
Object.assign(target, {
pressed: _this3.playing
});
}); // Only update controls on non timeupdate events
if (is$1.event(event) && event.type === 'timeupdate') {
@ -4504,13 +4495,13 @@ var ui = {
},
// Toggle controls based on state and `force` argument
toggleControls: function toggleControls(force) {
var controls = this.elements.controls;
var controlsElement = this.elements.controls;
if (controls && this.config.hideControls) {
if (controlsElement && this.config.hideControls) {
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
var recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek));
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
}
}
};
@ -4660,14 +4651,14 @@ function () {
break;
/* case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
default:
break;
@ -4778,11 +4769,11 @@ function () {
on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) {
var controls = elements.controls; // Remove button states for fullscreen
var controlsElement = elements.controls; // Remove button states for fullscreen
if (controls && event.type === 'enterfullscreen') {
controls.pressed = false;
controls.hover = false;
if (controlsElement && event.type === 'enterfullscreen') {
controlsElement.pressed = false;
controlsElement.hover = false;
} // Show, then hide after a timeout unless another control event occurs
@ -4801,15 +4792,7 @@ function () {
timers.controls = setTimeout(function () {
return ui.toggleControls.call(player, false);
}, delay);
}); // Force edge to repaint on exit fullscreen
// TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen
/* if (browser.isEdge) {
on.call(player, elements.container, 'exitfullscreen', () => {
setTimeout(() => repaint(elements.container), 100);
});
} */
// Set a gutter for Vimeo
}); // Set a gutter for Vimeo
var setGutter = function setGutter(ratio, padding, toggle) {
if (!player.isVimeo) {
@ -4844,16 +4827,21 @@ function () {
};
var resized = function resized() {
window.clearTimeout(timers.resized);
timers.resized = window.setTimeout(setPlayerSize, 50);
clearTimeout(timers.resized);
timers.resized = setTimeout(setPlayerSize, 50);
};
on.call(player, elements.container, 'enterfullscreen exitfullscreen', function (event) {
var _player$fullscreen = player.fullscreen,
target = _player$fullscreen.target,
usingNative = _player$fullscreen.usingNative; // Ignore for iOS native
usingNative = _player$fullscreen.usingNative; // Ignore events not from target
if (!player.isEmbed || target !== elements.container) {
if (target !== elements.container) {
return;
} // If it's not an embed and no ratio specified
if (!player.isEmbed && is$1.empty(player.config.ratio)) {
return;
}
@ -5247,7 +5235,6 @@ function () {
this.bind(elements.controls, 'focusin', function () {
var config = player.config,
elements = player.elements,
timers = player.timers; // Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle
@ -5989,23 +5976,22 @@ var youtube = {
if (is$1.object(window.YT) && is$1.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Load the API
loadScript(this.config.urls.youtube.sdk).catch(function (error) {
_this.debug.warn('YouTube API failed to load', error);
}); // Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; // Add to queue
window.onYouTubeReadyCallbacks.push(function () {
youtube.ready.call(_this);
}); // Set callback to process queue
// Reference current global callback
var callback = window.onYouTubeIframeAPIReady; // Set callback to process queue
window.onYouTubeIframeAPIReady = function () {
window.onYouTubeReadyCallbacks.forEach(function (callback) {
// Call global callback if set
if (is$1.function(callback)) {
callback();
});
};
}
youtube.ready.call(_this);
}; // Load the SDK
loadScript(this.config.urls.youtube.sdk).catch(function (error) {
_this.debug.warn('YouTube API failed to load', error);
});
}
},
// Get the media title
@ -6035,7 +6021,7 @@ var youtube = {
ready: function ready() {
var player = this; // Ignore already setup (race condition)
var currentId = player.media.getAttribute('id');
var currentId = player.media && player.media.getAttribute('id');
if (!is$1.empty(currentId) && currentId.startsWith('youtube-')) {
return;
@ -6060,8 +6046,8 @@ var youtube = {
});
player.media = replaceElement(container, player.media); // Id to poster wrapper
var posterSrc = function posterSrc(format) {
return "https://i.ytimg.com/vi/".concat(videoId, "/").concat(format, "default.jpg");
var posterSrc = function posterSrc(s) {
return "https://i.ytimg.com/vi/".concat(videoId, "/").concat(s, "default.jpg");
}; // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
@ -6074,9 +6060,9 @@ var youtube = {
}) // 360p padded 4:3. Always exists
.then(function (image) {
return ui.setPoster.call(player, image.src);
}).then(function (posterSrc) {
}).then(function (src) {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!posterSrc.includes('maxres')) {
if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
}).catch(function () {});
@ -6617,8 +6603,8 @@ function () {
}); // Advertisement regular events
Object.keys(google.ima.AdEvent.Type).forEach(function (type) {
_this6.manager.addEventListener(google.ima.AdEvent.Type[type], function (event) {
return _this6.onAdEvent(event);
_this6.manager.addEventListener(google.ima.AdEvent.Type[type], function (e) {
return _this6.onAdEvent(e);
});
}); // Resolve our adsManager
@ -6666,8 +6652,7 @@ function () {
var adData = event.getAdData(); // Proxy event
var dispatchEvent = function dispatchEvent(type) {
var event = "ads".concat(type.replace(/_/g, '').toLowerCase());
triggerEvent.call(_this8.player, _this8.player.media, event);
triggerEvent.call(_this8.player, _this8.player.media, "ads".concat(type.replace(/_/g, '').toLowerCase()));
}; // Bubble the event
@ -7123,7 +7108,11 @@ function () {
}
this.getThumbnails().then(function () {
// Render DOM elements
if (!_this.enabled) {
return;
} // Render DOM elements
_this.render(); // Check to see if thumb container size was specified manually in CSS
@ -7458,6 +7447,7 @@ function () {
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
// eslint-disable-next-line no-param-reassign
image.dataset.deleting = true; // This has to be set before the timeout - to prevent issues switching between hover and scrub
var currentImageContainer = _this8.currentImageContainer;
@ -7636,10 +7626,14 @@ function () {
} // Find difference between height and preview container height
var multiplier = this.thumbContainerHeight / frame.h;
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px");
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px");
previewImage.style.left = "-".concat(frame.x * multiplier, "px");
var multiplier = this.thumbContainerHeight / frame.h; // eslint-disable-next-line no-param-reassign
previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.left = "-".concat(frame.x * multiplier, "px"); // eslint-disable-next-line no-param-reassign
previewImage.style.top = "-".concat(frame.y * multiplier, "px");
}
}, {
@ -8471,32 +8465,32 @@ function () {
}, {
key: "isHTML5",
get: function get() {
return Boolean(this.provider === providers.html5);
return this.provider === providers.html5;
}
}, {
key: "isEmbed",
get: function get() {
return Boolean(this.isYouTube || this.isVimeo);
return this.isYouTube || this.isVimeo;
}
}, {
key: "isYouTube",
get: function get() {
return Boolean(this.provider === providers.youtube);
return this.provider === providers.youtube;
}
}, {
key: "isVimeo",
get: function get() {
return Boolean(this.provider === providers.vimeo);
return this.provider === providers.vimeo;
}
}, {
key: "isVideo",
get: function get() {
return Boolean(this.type === types.video);
return this.type === types.video;
}
}, {
key: "isAudio",
get: function get() {
return Boolean(this.type === types.audio);
return this.type === types.audio;
}
}, {
key: "playing",

2755
dist/plyr.polyfilled.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2755
dist/plyr.polyfilled.mjs vendored

File diff suppressed because it is too large Load Diff

View File

@ -6,24 +6,28 @@
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');
// ------------------------------------
// 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');
@ -37,18 +41,22 @@ 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';
// Get AWS config
@ -125,15 +133,16 @@ gulp.task(tasks.clean, done => {
// JavaScript
Object.entries(build.js).forEach(([filename, entry]) => {
entry.formats.forEach(format => {
const { dist, formats, namespace, polyfill, src } = entry;
formats.forEach(format => {
const name = `js:${filename}:${format}`;
tasks.js.push(name);
const polyfill = filename.includes('polyfilled');
const extension = format === 'es' ? 'mjs' : 'js';
tasks.js.push(name);
gulp.task(name, () =>
gulp
.src(entry.src)
.src(src)
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(
@ -159,7 +168,7 @@ Object.entries(build.js).forEach(([filename, entry]) => {
],
},
{
name: entry.namespace,
name: namespace,
format,
},
),
@ -170,25 +179,26 @@ Object.entries(build.js).forEach(([filename, entry]) => {
extname: `.${extension}`,
}),
)
.pipe(gulp.dest(entry.dist))
.pipe(gulp.dest(dist))
.pipe(filter(`**/*.${extension}`))
.pipe(terser())
.pipe(rename({ suffix: minSuffix }))
.pipe(size(sizeOptions))
.pipe(sourcemaps.write(''))
.pipe(gulp.dest(entry.dist)),
.pipe(gulp.dest(dist)),
);
});
});
// CSS
Object.entries(build.css).forEach(([filename, entry]) => {
const { dist, src } = entry;
const name = `css:${filename}`;
tasks.css.push(name);
gulp.task(name, () =>
gulp
.src(entry.src)
.src(src)
.pipe(plumber())
.pipe(sass())
.pipe(
@ -198,24 +208,25 @@ Object.entries(build.css).forEach(([filename, entry]) => {
)
.pipe(clean())
.pipe(size(sizeOptions))
.pipe(gulp.dest(entry.dist)),
.pipe(gulp.dest(dist)),
);
});
// SVG Sprites
Object.entries(build.sprite).forEach(([filename, entry]) => {
const { dist, src } = entry;
const name = `sprite:${filename}`;
tasks.sprite.push(name);
gulp.task(name, () =>
gulp
.src(entry.src)
.src(src)
.pipe(plumber())
.pipe(imagemin())
.pipe(svgstore())
.pipe(rename({ basename: path.parse(filename).name }))
.pipe(size(sizeOptions))
.pipe(gulp.dest(entry.dist)),
.pipe(gulp.dest(dist)),
);
});

View File

@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.5.4",
"version": "3.5.5",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"author": "Sam Potts <sam@potts.es>",
@ -36,28 +36,28 @@
"deploy": "yarn lint && gulp deploy"
},
"devDependencies": {
"ansi-colors": "^3.2.4",
"aws-sdk": "^2.445.0",
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"babel-eslint": "^10.0.1",
"ansi-colors": "^4.0.1",
"aws-sdk": "^2.478.0",
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"babel-eslint": "^10.0.2",
"del": "^4.1.1",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-simple-import-sort": "^3.1.0",
"eslint-config-prettier": "^5.0.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-simple-import-sort": "^4.0.0",
"fancy-log": "^1.3.3",
"fastly-purge": "^1.0.1",
"git-branch": "^2.0.1",
"gulp": "^4.0.1",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^6.1.0",
"gulp-awspublish": "^4.0.0",
"gulp-better-rollup": "^4.0.1",
"gulp-clean-css": "^4.2.0",
"gulp-filter": "^5.1.0",
"gulp-filter": "^6.0.0",
"gulp-header": "^2.0.7",
"gulp-imagemin": "^5.0.3",
"gulp-imagemin": "^6.0.0",
"gulp-open": "^3.0.1",
"gulp-plumber": "^1.2.1",
"gulp-postcss": "^8.0.0",
@ -67,31 +67,30 @@
"gulp-size": "^3.0.0",
"gulp-sourcemaps": "^2.6.5",
"gulp-svgstore": "^7.0.1",
"gulp-terser": "^1.1.7",
"postcss-custom-properties": "^8.0.10",
"prettier-eslint": "^8.8.2",
"gulp-terser": "^1.2.0",
"postcss-custom-properties": "^8.0.11",
"prettier-eslint": "^9.0.0",
"prettier-stylelint": "^0.4.2",
"remark-cli": "^6.0.1",
"remark-validate-links": "^8.0.2",
"rollup": "^1.10.1",
"remark-validate-links": "^8.0.3",
"rollup": "^1.15.6",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.3",
"stylelint": "^10.0.1",
"stylelint-config-prettier": "^5.1.0",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-resolve": "^5.0.3",
"stylelint": "^10.1.0",
"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.6.0",
"stylelint-scss": "^3.8.0",
"stylelint-selector-bem-pattern": "^2.1.0",
"through2": "^3.0.1"
},
"dependencies": {
"core-js": "^3.0.1",
"core-js": "^3.1.4",
"custom-event-polyfill": "^1.0.7",
"loadjs": "^3.6.1",
"rangetouch": "^2.0.0",
"raven-js": "^3.27.0",
"url-polyfill": "^1.1.5"
}
}

View File

@ -23,9 +23,6 @@
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
// Trim on save
"files.trimTrailingWhitespace": true

View File

@ -6,13 +6,12 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
# 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
- 😎 **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 +24,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 +108,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');
```
Alertnatively you can include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
```html
<script src="path/to/plyr.js"></script>
@ -123,18 +130,18 @@ See [initialising](#initialising) for more information on advanced setups.
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
```html
<script src="https://cdn.plyr.io/3.5.3/plyr.js"></script>
<script src="https://cdn.plyr.io/3.5.4/plyr.js"></script>
```
...or...
```html
<script src="https://cdn.plyr.io/3.5.3/plyr.polyfilled.js"></script>
<script src="https://cdn.plyr.io/3.5.4/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 +150,13 @@ Include the `plyr.css` stylsheet into your `<head>`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.3/plyr.css" />
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.4/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.3/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.4/plyr.svg`.
# Ads
@ -204,7 +211,7 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
You can specify a range of arguments for the constructor to use:
- 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
@ -238,7 +245,7 @@ You have two choices here. You can either use a simple array loop to map the con
const players = Array.from(document.querySelectorAll('.js-player')).map(p => new Plyr(p));
```
...or use a static method where you can pass a string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), 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:
...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');
@ -703,6 +710,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,7 +730,7 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
- [Sparkk TV](https://www.sparkktv.com/)
- [@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

View File

@ -133,6 +133,7 @@ const captions = {
});
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line no-param-reassign
track.mode = 'hidden';
// Add event listener for cue changes
@ -302,7 +303,7 @@ const captions = {
let track;
languages.every(language => {
track = sorted.find(track => track.language === language);
track = sorted.find(t => t.language === language);
return !track; // Break iteration if there is a match
});

View File

@ -128,6 +128,7 @@ const defaults = {
// 'fast-forward',
'progress',
'current-time',
// 'duration',
'mute',
'volume',
'captions',
@ -195,7 +196,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',

50
src/js/controls.js vendored
View File

@ -11,7 +11,20 @@ import support from './support';
import { repaint, transitionEndEvent } from './utils/animation';
import { dedupe } from './utils/arrays';
import browser from './utils/browser';
import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, matches, removeElement, setAttributes, setFocus, toggleClass, toggleHidden } from './utils/elements';
import {
createElement,
emptyElement,
getAttributesFromSelector,
getElement,
getElements,
hasClass,
matches,
removeElement,
setAttributes,
setFocus,
toggleClass,
toggleHidden,
} from './utils/elements';
import { off, on } from './utils/events';
import i18n from './utils/i18n';
import is from './utils/is';
@ -480,15 +493,15 @@ const controls = {
get() {
return menuItem.getAttribute('aria-checked') === 'true';
},
set(checked) {
set(check) {
// Ensure exclusivity
if (checked) {
if (check) {
Array.from(menuItem.parentNode.children)
.filter(node => matches(node, '[role="menuitemradio"]'))
.forEach(node => node.setAttribute('aria-checked', 'false'));
}
menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
},
});
@ -596,17 +609,17 @@ const controls = {
let value = 0;
const setProgress = (target, input) => {
const value = is.number(input) ? input : 0;
const val = is.number(input) ? input : 0;
const progress = is.element(target) ? target : this.elements.display.buffer;
// Update value and label
if (is.element(progress)) {
progress.value = value;
progress.value = val;
// Update text label inside
const label = progress.getElementsByTagName('span')[0];
if (is.element(label)) {
label.childNodes[0].nodeValue = value;
label.childNodes[0].nodeValue = val;
}
}
};
@ -688,14 +701,8 @@ const controls = {
return;
}
// Calculate percentage
let percent = 0;
const clientRect = this.elements.progress.getBoundingClientRect();
const visible = `${this.config.classNames.tooltip}--visible`;
const toggle = toggle => {
toggleClass(this.elements.display.seekTooltip, visible, toggle);
};
const toggle = show => toggleClass(this.elements.display.seekTooltip, visible, show);
// Hide on touch
if (this.touch) {
@ -704,6 +711,9 @@ const controls = {
}
// Determine percentage, if already visible
let percent = 0;
const clientRect = this.elements.progress.getBoundingClientRect();
if (is.event(event)) {
percent = (100 / clientRect.width) * (event.pageX - clientRect.left);
} else if (hasClass(this.elements.display.seekTooltip, visible)) {
@ -1100,7 +1110,7 @@ const controls = {
let target = pane;
if (!is.element(target)) {
target = Object.values(this.elements.settings.panels).find(pane => !pane.hidden);
target = Object.values(this.elements.settings.panels).find(p => !p.hidden);
}
const firstItem = target.querySelector('[role^="menuitem"]');
@ -1398,7 +1408,7 @@ const controls = {
// Settings button / menu
if (control === 'settings' && !is.empty(this.config.settings)) {
const control = createElement(
const wrapper = createElement(
'div',
extend({}, defaultAttributes, {
class: `${defaultAttributes.class} plyr__menu`.trim(),
@ -1406,7 +1416,7 @@ const controls = {
}),
);
control.appendChild(
wrapper.appendChild(
createButton.call(this, 'settings', {
'aria-haspopup': true,
'aria-controls': `plyr-settings-${data.id}`,
@ -1546,11 +1556,11 @@ const controls = {
});
popup.appendChild(inner);
control.appendChild(popup);
container.appendChild(control);
wrapper.appendChild(popup);
container.appendChild(wrapper);
this.elements.settings.popup = popup;
this.elements.settings.menu = control;
this.elements.settings.menu = wrapper;
}
// Picture in picture button

View File

@ -73,9 +73,6 @@ function toggleFallback(toggle = false) {
.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

View File

@ -44,15 +44,17 @@ const html5 = {
const player = this;
// Set aspect ratio if set
setAspectRatio.call(player);
// Set aspect ratio if fixed
if (!is.empty(this.config.ratio)) {
setAspectRatio.call(player);
}
// Quality
Object.defineProperty(player.media, 'quality', {
get() {
// Get sources
const sources = html5.getSources.call(player);
const source = sources.find(source => source.getAttribute('src') === player.source);
const source = sources.find(s => s.getAttribute('src') === player.source);
// Return size, if match is found
return source && Number(source.getAttribute('size'));
@ -61,7 +63,7 @@ const html5 = {
// Get sources
const sources = html5.getSources.call(player);
// Get first match for requested size
const source = sources.find(source => Number(source.getAttribute('size')) === input);
const source = sources.find(s => Number(s.getAttribute('size')) === input);
// No matching source found
if (!source) {

View File

@ -147,7 +147,7 @@ class Listeners {
player.loop = !player.loop;
break;
/* case 73:
/* case 73:
this.setLoop('start');
break;
@ -275,12 +275,12 @@ class Listeners {
elements.container,
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
event => {
const { controls } = elements;
const { controls: controlsElement } = elements;
// Remove button states for fullscreen
if (controls && event.type === 'enterfullscreen') {
controls.pressed = false;
controls.hover = false;
if (controlsElement && event.type === 'enterfullscreen') {
controlsElement.pressed = false;
controlsElement.hover = false;
}
// Show, then hide after a timeout unless another control event occurs
@ -301,14 +301,6 @@ class Listeners {
},
);
// Force edge to repaint on exit fullscreen
// TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen
/* if (browser.isEdge) {
on.call(player, elements.container, 'exitfullscreen', () => {
setTimeout(() => repaint(elements.container), 100);
});
} */
// Set a gutter for Vimeo
const setGutter = (ratio, padding, toggle) => {
if (!player.isVimeo) {
@ -337,15 +329,20 @@ class Listeners {
};
const resized = () => {
window.clearTimeout(timers.resized);
timers.resized = window.setTimeout(setPlayerSize, 50);
clearTimeout(timers.resized);
timers.resized = setTimeout(setPlayerSize, 50);
};
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;
}
@ -801,7 +798,7 @@ class Listeners {
// Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin', () => {
const { config, elements, timers } = player;
const { config, timers } = player;
// Skip transition to prevent focus from scrolling the parent element
toggleClass(elements.controls, config.classNames.noTransition, true);

View File

@ -267,7 +267,7 @@ class Ads {
// Advertisement regular events
Object.keys(google.ima.AdEvent.Type).forEach(type => {
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
});
// Resolve our adsManager
@ -310,8 +310,7 @@ class Ads {
// Proxy event
const dispatchEvent = type => {
const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
triggerEvent.call(this.player, this.player.media, event);
triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
};
// Bubble the event

View File

@ -2,6 +2,7 @@ import { createElement } from '../utils/elements';
import { once } from '../utils/events';
import fetch from '../utils/fetch';
import is from '../utils/is';
import { extend } from '../utils/objects';
import { formatTime } from '../utils/time';
// Arg: vttDataString example: "WEBVTT\n\n1\n00:00:05.000 --> 00:00:10.000\n1080p-00001.jpg"
@ -100,6 +101,10 @@ class PreviewThumbnails {
}
this.getThumbnails().then(() => {
if (!this.enabled) {
return;
}
// Render DOM elements
this.render();
@ -425,7 +430,9 @@ class PreviewThumbnails {
if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
// Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
// First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
// eslint-disable-next-line no-param-reassign
image.dataset.deleting = true;
// This has to be set before the timeout - to prevent issues switching between hover and scrub
const { currentImageContainer } = this;
@ -632,9 +639,13 @@ class PreviewThumbnails {
// Find difference between height and preview container height
const multiplier = this.thumbContainerHeight / frame.h;
// eslint-disable-next-line no-param-reassign
previewImage.style.height = `${Math.floor(previewImage.naturalHeight * multiplier)}px`;
// eslint-disable-next-line no-param-reassign
previewImage.style.width = `${Math.floor(previewImage.naturalWidth * multiplier)}px`;
// eslint-disable-next-line no-param-reassign
previewImage.style.left = `-${frame.x * multiplier}px`;
// eslint-disable-next-line no-param-reassign
previewImage.style.top = `-${frame.y * multiplier}px`;
}
}

View File

@ -56,26 +56,23 @@ const youtube = {
if (is.object(window.YT) && is.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Load the API
loadScript(this.config.urls.youtube.sdk).catch(error => {
this.debug.warn('YouTube API failed to load', error);
});
// Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue
window.onYouTubeReadyCallbacks.push(() => {
youtube.ready.call(this);
});
// Reference current global callback
const callback = window.onYouTubeIframeAPIReady;
// Set callback to process queue
window.onYouTubeIframeAPIReady = () => {
window.onYouTubeReadyCallbacks.forEach(callback => {
// Call global callback if set
if (is.function(callback)) {
callback();
});
}
youtube.ready.call(this);
};
// Load the SDK
loadScript(this.config.urls.youtube.sdk).catch(error => {
this.debug.warn('YouTube API failed to load', error);
});
}
},
@ -108,7 +105,7 @@ const youtube = {
ready() {
const player = this;
// Ignore already setup (race condition)
const currentId = player.media.getAttribute('id');
const currentId = player.media && player.media.getAttribute('id');
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
return;
}
@ -131,16 +128,16 @@ const youtube = {
player.media = replaceElement(container, player.media);
// Id to poster wrapper
const posterSrc = format => `https://i.ytimg.com/vi/${videoId}/${format}default.jpg`;
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
.then(image => ui.setPoster.call(player, image.src))
.then(posterSrc => {
.then(src => {
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
if (!posterSrc.includes('maxres')) {
if (!src.includes('maxres')) {
player.elements.poster.style.backgroundSize = 'cover';
}
})

View File

@ -323,27 +323,27 @@ class Plyr {
* Types and provider helpers
*/
get isHTML5() {
return Boolean(this.provider === providers.html5);
return this.provider === providers.html5;
}
get isEmbed() {
return Boolean(this.isYouTube || this.isVimeo);
return this.isYouTube || this.isVimeo;
}
get isYouTube() {
return Boolean(this.provider === providers.youtube);
return this.provider === providers.youtube;
}
get isVimeo() {
return Boolean(this.provider === providers.vimeo);
return this.provider === providers.vimeo;
}
get isVideo() {
return Boolean(this.type === types.video);
return this.type === types.video;
}
get isAudio() {
return Boolean(this.type === types.audio);
return this.type === types.audio;
}
/**

View File

@ -213,7 +213,7 @@ const ui = {
// Set state
Array.from(this.elements.buttons.play || []).forEach(target => {
target.pressed = this.playing;
Object.assign(target, { pressed: this.playing });
});
// Only update controls on non timeupdate events
@ -247,15 +247,22 @@ const ui = {
// Toggle controls based on state and `force` argument
toggleControls(force) {
const { controls } = this.elements;
const { controls: controlsElement } = this.elements;
if (controls && this.config.hideControls) {
if (controlsElement && this.config.hideControls) {
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
this.toggleControls(
Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek),
Boolean(
force ||
this.loading ||
this.paused ||
controlsElement.pressed ||
controlsElement.hover ||
recentTouchSeek,
),
);
}
},

View File

@ -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);
}

View File

@ -192,11 +192,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
@ -231,14 +228,14 @@ export function matches(element, selector) {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
const matches =
const method =
prototype.matches ||
prototype.webkitMatchesSelector ||
prototype.mozMatchesSelector ||
prototype.msMatchesSelector ||
match;
return matches.call(element, selector);
return method.call(element, selector);
}
// Find all elements

View File

@ -36,8 +36,8 @@ const i18n = {
'{title}': config.title,
};
Object.entries(replace).forEach(([key, value]) => {
string = replaceAll(string, key, value);
Object.entries(replace).forEach(([k, v]) => {
string = replaceAll(string, k, v);
});
return string;

View File

@ -18,6 +18,7 @@ export default function loadSprite(url, id) {
const exists = () => document.getElementById(id) !== null;
const update = (container, data) => {
// eslint-disable-next-line no-param-reassign
container.innerHTML = data;
// Check again incase of race condition

View File

@ -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

View File

@ -63,10 +63,6 @@ a.plyr__control {
// 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,
@ -81,7 +77,6 @@ a.plyr__control {
background: rgba($plyr-video-control-bg-hover, 0.8);
border: 0;
border-radius: 100%;
box-shadow: 0 1px 1px rgba(#000, 0.15);
color: $plyr-video-control-color;
display: none;
left: 50%;

View File

@ -12,6 +12,7 @@
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 {

View File

@ -62,12 +62,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%);
}

View File

@ -23,7 +23,7 @@
// The countdown label
&::after {
background: rgba($plyr-color-gunmetal, 0.8);
background: rgba($plyr-color-gray-9, 0.8);
border-radius: 2px;
bottom: $plyr-control-spacing;
color: #fff;

View File

@ -7,7 +7,7 @@ $plyr-preview-bg: $plyr-tooltip-bg !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-bg: $plyr-color-gray-2 !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);

View File

@ -2,5 +2,5 @@
// Badges
// ==========================================================================
$plyr-badge-bg: $plyr-color-fiord !default;
$plyr-badge-bg: $plyr-color-gray-7 !default;
$plyr-badge-color: #fff !default;

View File

@ -2,8 +2,16 @@
// Colors
// ==========================================================================
$plyr-color-main: #1aafff !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-9: hsl(210, 15%, 16%);
$plyr-color-gray-8: lighten($plyr-color-gray-9, 9%);
$plyr-color-gray-7: lighten($plyr-color-gray-8, 9%);
$plyr-color-gray-6: lighten($plyr-color-gray-7, 9%);
$plyr-color-gray-5: lighten($plyr-color-gray-6, 9%);
$plyr-color-gray-4: lighten($plyr-color-gray-5, 9%);
$plyr-color-gray-3: lighten($plyr-color-gray-4, 9%);
$plyr-color-gray-2: lighten($plyr-color-gray-3, 9%);
$plyr-color-gray-1: lighten($plyr-color-gray-2, 9%);
$plyr-color-gray-0: lighten($plyr-color-gray-1, 9%);

View File

@ -13,6 +13,6 @@ $plyr-video-control-color-hover: #fff !default;
$plyr-video-control-bg-hover: $plyr-color-main !default;
$plyr-audio-controls-bg: #fff !default;
$plyr-audio-control-color: $plyr-color-fiord !default;
$plyr-audio-control-color: $plyr-color-gray-7 !default;
$plyr-audio-control-color-hover: #fff !default;
$plyr-audio-control-bg-hover: $plyr-color-main !default;

View File

@ -3,8 +3,8 @@
// ==========================================================================
$plyr-menu-bg: rgba(#fff, 0.9) !default;
$plyr-menu-color: $plyr-color-fiord !default;
$plyr-menu-color: $plyr-color-gray-7 !default;
$plyr-menu-arrow-size: 6px !default;
$plyr-menu-border-color: $plyr-color-heather !default;
$plyr-menu-border-color: $plyr-color-gray-2 !default;
$plyr-menu-border-shadow-color: #fff !default;
$plyr-menu-shadow: 0 1px 2px rgba(#000, 0.15) !default;

View File

@ -4,8 +4,8 @@
// Loading
$plyr-progress-loading-size: 25px !default;
$plyr-progress-loading-bg: rgba($plyr-color-gunmetal, 0.6) !default;
$plyr-progress-loading-bg: rgba($plyr-color-gray-9, 0.6) !default;
// Buffered
$plyr-video-progress-buffered-bg: rgba(#fff, 0.25) !default;
$plyr-audio-progress-buffered-bg: rgba($plyr-color-heather, 0.66) !default;
$plyr-audio-progress-buffered-bg: rgba($plyr-color-gray-2, 0.66) !default;

View File

@ -9,7 +9,7 @@ $plyr-range-thumb-active-shadow-width: 3px !default;
$plyr-range-thumb-height: 13px !default;
$plyr-range-thumb-bg: #fff !default;
$plyr-range-thumb-border: 2px solid transparent !default;
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gray-9, 0.2) !default;
// Track
$plyr-range-track-height: 5px !default;

View File

@ -3,7 +3,7 @@
// ==========================================================================
$plyr-tooltip-bg: rgba(#fff, 0.9) !default;
$plyr-tooltip-color: $plyr-color-fiord !default;
$plyr-tooltip-color: $plyr-color-gray-7 !default;
$plyr-tooltip-padding: ($plyr-control-spacing / 2) !default;
$plyr-tooltip-arrow-size: 4px !default;
$plyr-tooltip-radius: 3px !default;

1970
yarn.lock

File diff suppressed because it is too large Load Diff