Converted to 2 space indentation
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
||||||
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"selector-class-pattern": null,
|
"selector-class-pattern": null,
|
||||||
"selector-no-qualifying-type": [
|
"selector-no-qualifying-type": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"ignore": ["attribute", "class"]
|
"ignore": ["attribute", "class"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"string-no-newline": null,
|
"string-no-newline": null,
|
||||||
"indentation": 4,
|
"indentation": 2,
|
||||||
"string-quotes": "single",
|
"string-quotes": "single",
|
||||||
"max-nesting-depth": 2,
|
"max-nesting-depth": 2,
|
||||||
"plugin/selector-bem-pattern": {
|
"plugin/selector-bem-pattern": {
|
||||||
"preset": "bem",
|
"preset": "bem",
|
||||||
"componentName": "(([a-z0-9]+(?!-$)-?)+)",
|
"componentName": "(([a-z0-9]+(?!-$)-?)+)",
|
||||||
"componentSelectors": {
|
"componentSelectors": {
|
||||||
"initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
|
"initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
|
||||||
},
|
},
|
||||||
"ignoreSelectors": [".*\\.has-.*", ".*\\.is-.*"]
|
"ignoreSelectors": [".*\\.has-.*", ".*\\.is-.*"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+70
-70
@@ -21299,19 +21299,19 @@ typeof navigator === "object" && (function () {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -22418,39 +22418,39 @@ typeof navigator === "object" && (function () {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -28987,41 +28987,41 @@ typeof navigator === "object" && (function () {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+239
-248
@@ -1,278 +1,269 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="A simple HTML5 media player with custom controls and WebVTT captions."
|
content="A simple HTML5 media player with custom controls and WebVTT captions."
|
||||||
/>
|
/>
|
||||||
<meta name="author" content="Sam Potts" />
|
<meta name="author" content="Sam Potts" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
|
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
||||||
|
|
||||||
<!-- Open Graph -->
|
<!-- Open Graph -->
|
||||||
<meta
|
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player" />
|
||||||
property="og:title"
|
<meta property="og:site_name" content="Plyr" />
|
||||||
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
<meta property="og:url" content="https://plyr.io" />
|
||||||
/>
|
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
|
||||||
<meta property="og:site_name" content="Plyr" />
|
|
||||||
<meta property="og:url" content="https://plyr.io" />
|
|
||||||
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@sam_potts" />
|
<meta name="twitter:site" content="@sam_potts" />
|
||||||
<meta name="twitter:creator" content="@sam_potts" />
|
<meta name="twitter:creator" content="@sam_potts" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
||||||
<!-- Docs styles -->
|
<!-- Docs styles -->
|
||||||
<link rel="stylesheet" href="dist/demo.css" />
|
<link rel="stylesheet" href="dist/demo.css" />
|
||||||
|
|
||||||
<!-- Preload -->
|
<!-- Preload -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="font"
|
as="font"
|
||||||
crossorigin
|
crossorigin
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
|
href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="font"
|
as="font"
|
||||||
crossorigin
|
crossorigin
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Google Analytics-->
|
<!-- Google Analytics-->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag() {
|
function gtag() {
|
||||||
dataLayer.push(arguments);
|
dataLayer.push(arguments);
|
||||||
}
|
}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
gtag('config', 'UA-132699580-1');
|
gtag('config', 'UA-132699580-1');
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<header>
|
<header>
|
||||||
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
||||||
<p>
|
<p>
|
||||||
A simple, accessible and customisable media player for
|
A simple, accessible and customisable media player for
|
||||||
<button type="button" class="faux-link" data-source="video">
|
<button type="button" class="faux-link" data-source="video">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path></svg
|
></path></svg
|
||||||
>Video</button
|
>Video</button
|
||||||
>,
|
>,
|
||||||
<button type="button" class="faux-link" data-source="audio">
|
<button type="button" class="faux-link" data-source="audio">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path></svg
|
></path></svg
|
||||||
>Audio</button
|
>Audio</button
|
||||||
>,
|
>,
|
||||||
<button type="button" class="faux-link" data-source="youtube">
|
<button type="button" class="faux-link" data-source="youtube">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>YouTube</title>
|
<title>YouTube</title>
|
||||||
<path
|
<path
|
||||||
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||||
M6,11V5l5,3L6,11z"
|
M6,11V5l5,3L6,11z"
|
||||||
></path></svg
|
></path></svg
|
||||||
>YouTube
|
>YouTube
|
||||||
</button>
|
</button>
|
||||||
and
|
and
|
||||||
<button type="button" class="faux-link" data-source="vimeo">
|
<button type="button" class="faux-link" data-source="vimeo">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>Vimeo</title>
|
<title>Vimeo</title>
|
||||||
<path
|
<path
|
||||||
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
||||||
></path></svg
|
></path></svg
|
||||||
>Vimeo
|
>Vimeo
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Premium video monetization from
|
Premium video monetization from
|
||||||
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
||||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
||||||
<span class="sr-only">ai.vi</span>
|
<span class="sr-only">ai.vi</span>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="call-to-action">
|
<div class="call-to-action">
|
||||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>GitHub</title>
|
<title>GitHub</title>
|
||||||
<path
|
<path
|
||||||
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
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
|
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
|
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
|
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.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"
|
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>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
Download on GitHub
|
Download on GitHub
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<!-- style="--plyr-color-main: #47bb4d; --plyr-video-control-background-hover: var(--plyr-color-main); " -->
|
<!-- style="--plyr-color-main: #47bb4d; --plyr-video-control-background-hover: var(--plyr-color-main); " -->
|
||||||
<div id="container" style="--plyr-color-main: #1ac266;">
|
<div id="container" style="--plyr-color-main: #1ac266;">
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
crossorigin
|
crossorigin
|
||||||
playsinline
|
playsinline
|
||||||
poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
||||||
id="player"
|
id="player"
|
||||||
>
|
>
|
||||||
<!-- Video files -->
|
<!-- Video files -->
|
||||||
<source
|
<source
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
||||||
type="video/mp4"
|
type="video/mp4"
|
||||||
size="576"
|
size="576"
|
||||||
/>
|
/>
|
||||||
<source
|
<source
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4"
|
||||||
type="video/mp4"
|
type="video/mp4"
|
||||||
size="720"
|
size="720"
|
||||||
/>
|
/>
|
||||||
<source
|
<source
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4"
|
||||||
type="video/mp4"
|
type="video/mp4"
|
||||||
size="1080"
|
size="1080"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Caption files -->
|
<!-- Caption files -->
|
||||||
<track
|
<track
|
||||||
kind="captions"
|
kind="captions"
|
||||||
label="English"
|
label="English"
|
||||||
srclang="en"
|
srclang="en"
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||||
default
|
default
|
||||||
/>
|
/>
|
||||||
<track
|
<track
|
||||||
kind="captions"
|
kind="captions"
|
||||||
label="Français"
|
label="Français"
|
||||||
srclang="fr"
|
srclang="fr"
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
<!-- Fallback for browsers that don't support the <video> element -->
|
||||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download
|
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
|
||||||
>Download</a
|
</video>
|
||||||
>
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li class="plyr__cite plyr__cite--video" hidden>
|
|
||||||
<small>
|
|
||||||
<svg class="icon">
|
|
||||||
<title>HTML5</title>
|
|
||||||
<path
|
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<a
|
|
||||||
href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323"
|
|
||||||
target="_blank"
|
|
||||||
>View From A Blue Moon</a
|
|
||||||
>
|
|
||||||
© Brainfarm
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--audio" hidden>
|
|
||||||
<small>
|
|
||||||
<svg class="icon" title="HTML5">
|
|
||||||
<title>HTML5</title>
|
|
||||||
<path
|
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<a href="http://www.kishibashi.com/" target="_blank"
|
|
||||||
>Kishi Bashi – “It All Began With A Burst”</a
|
|
||||||
>
|
|
||||||
© Kishi Bashi
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--youtube" hidden>
|
|
||||||
<small>
|
|
||||||
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank"
|
|
||||||
>View From A Blue Moon</a
|
|
||||||
>
|
|
||||||
on
|
|
||||||
<span class="color--youtube">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>YouTube</title>
|
|
||||||
<path
|
|
||||||
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
|
||||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
|
||||||
M6,11V5l5,3L6,11z"
|
|
||||||
></path></svg
|
|
||||||
>YouTube
|
|
||||||
</span>
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
|
||||||
<small>
|
|
||||||
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
|
||||||
on
|
|
||||||
<span class="color--vimeo">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>Vimeo</title>
|
|
||||||
<path
|
|
||||||
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
|
||||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
|
||||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
|
||||||
></path></svg
|
|
||||||
>Vimeo
|
|
||||||
</span>
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside>
|
<ul>
|
||||||
<svg class="icon">
|
<li class="plyr__cite plyr__cite--video" hidden>
|
||||||
<title>Twitter</title>
|
<small>
|
||||||
|
<svg class="icon">
|
||||||
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank"
|
||||||
|
>View From A Blue Moon</a
|
||||||
|
>
|
||||||
|
© Brainfarm
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--audio" hidden>
|
||||||
|
<small>
|
||||||
|
<svg class="icon" title="HTML5">
|
||||||
|
<title>HTML5</title>
|
||||||
|
<path
|
||||||
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<a href="http://www.kishibashi.com/" target="_blank"
|
||||||
|
>Kishi Bashi – “It All Began With A Burst”</a
|
||||||
|
>
|
||||||
|
© Kishi Bashi
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--youtube" hidden>
|
||||||
|
<small>
|
||||||
|
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a>
|
||||||
|
on
|
||||||
|
<span class="color--youtube">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>YouTube</title>
|
||||||
|
<path
|
||||||
|
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||||
|
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||||
|
M6,11V5l5,3L6,11z"
|
||||||
|
></path></svg
|
||||||
|
>YouTube
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||||
|
<small>
|
||||||
|
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
||||||
|
on
|
||||||
|
<span class="color--vimeo">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>Vimeo</title>
|
||||||
|
<path
|
||||||
|
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||||
|
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||||
|
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
||||||
|
></path></svg
|
||||||
|
>Vimeo
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<svg class="icon">
|
||||||
|
<title>Twitter</title>
|
||||||
|
<path
|
||||||
|
d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
||||||
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
||||||
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
||||||
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
|
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>
|
<p>
|
||||||
If you think Plyr's good,
|
If you think Plyr's good,
|
||||||
<a
|
<a
|
||||||
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="js-shr"
|
class="js-shr"
|
||||||
>tweet it</a
|
>tweet it</a
|
||||||
>
|
>
|
||||||
👍
|
👍
|
||||||
</p>
|
</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+119
-122
@@ -16,137 +16,134 @@ import sources from './sources';
|
|||||||
import toggleClass from './toggle-class';
|
import toggleClass from './toggle-class';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const production = 'plyr.io';
|
const production = 'plyr.io';
|
||||||
|
|
||||||
// Sentry for demo site (https://plyr.io) only
|
// Sentry for demo site (https://plyr.io) only
|
||||||
if (window.location.host === production) {
|
if (window.location.host === production) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555',
|
dsn: 'https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555',
|
||||||
whitelistUrls: [production].map(d => new RegExp(`https://(([a-z0-9])+(.))*${d}`)),
|
whitelistUrls: [production].map(d => new RegExp(`https://(([a-z0-9])+(.))*${d}`)),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const selector = '#player';
|
||||||
|
|
||||||
|
// Setup share buttons
|
||||||
|
Shr.setup('.js-shr', {
|
||||||
|
count: {
|
||||||
|
className: 'button__count',
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
className: 'button--with-count',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the player
|
||||||
|
const player = new Plyr(selector, {
|
||||||
|
debug: true,
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
iconUrl: 'dist/demo.svg',
|
||||||
|
keyboard: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
controls: true,
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
ads: {
|
||||||
|
enabled: window.location.host.includes(production),
|
||||||
|
publisherId: '918848828995742',
|
||||||
|
},
|
||||||
|
previewThumbnails: {
|
||||||
|
enabled: true,
|
||||||
|
src: ['https://cdn.plyr.io/static/demo/thumbs/100p.vtt', 'https://cdn.plyr.io/static/demo/thumbs/240p.vtt'],
|
||||||
|
},
|
||||||
|
vimeo: {
|
||||||
|
// Prevent Vimeo blocking plyr.io demo site
|
||||||
|
referrerPolicy: 'no-referrer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose for tinkering in the console
|
||||||
|
window.player = player;
|
||||||
|
|
||||||
|
// Setup type toggle
|
||||||
|
const buttons = document.querySelectorAll('[data-source]');
|
||||||
|
const types = Object.keys(sources);
|
||||||
|
const historySupport = Boolean(window.history && window.history.pushState);
|
||||||
|
let currentType = window.location.hash.substring(1);
|
||||||
|
const hasInitialType = currentType.length;
|
||||||
|
|
||||||
|
function render(type) {
|
||||||
|
// Remove active classes
|
||||||
|
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
|
// Set active on parent
|
||||||
|
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
||||||
|
|
||||||
|
// Show cite
|
||||||
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
cite.hidden = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector(`.plyr__cite--${type}`).hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
// Set a new source
|
||||||
const selector = '#player';
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup share buttons
|
// Set the new source
|
||||||
Shr.setup('.js-shr', {
|
player.source = sources[type];
|
||||||
count: {
|
|
||||||
className: 'button__count',
|
|
||||||
},
|
|
||||||
wrapper: {
|
|
||||||
className: 'button--with-count',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup the player
|
// Set the current type for next time
|
||||||
const player = new Plyr(selector, {
|
currentType = type;
|
||||||
debug: true,
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
iconUrl: 'dist/demo.svg',
|
|
||||||
keyboard: {
|
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
controls: true,
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
ads: {
|
|
||||||
enabled: window.location.host.includes(production),
|
|
||||||
publisherId: '918848828995742',
|
|
||||||
},
|
|
||||||
previewThumbnails: {
|
|
||||||
enabled: true,
|
|
||||||
src: [
|
|
||||||
'https://cdn.plyr.io/static/demo/thumbs/100p.vtt',
|
|
||||||
'https://cdn.plyr.io/static/demo/thumbs/240p.vtt',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
vimeo: {
|
|
||||||
// Prevent Vimeo blocking plyr.io demo site
|
|
||||||
referrerPolicy: 'no-referrer',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose for tinkering in the console
|
render(type);
|
||||||
window.player = player;
|
}
|
||||||
|
|
||||||
// Setup type toggle
|
// Bind to each button
|
||||||
const buttons = document.querySelectorAll('[data-source]');
|
Array.from(buttons).forEach(button => {
|
||||||
const types = Object.keys(sources);
|
button.addEventListener('click', () => {
|
||||||
const historySupport = Boolean(window.history && window.history.pushState);
|
const type = button.getAttribute('data-source');
|
||||||
let currentType = window.location.hash.substring(1);
|
|
||||||
const hasInitialType = currentType.length;
|
|
||||||
|
|
||||||
function render(type) {
|
setSource(type);
|
||||||
// Remove active classes
|
|
||||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
|
||||||
|
|
||||||
// Set active on parent
|
if (historySupport) {
|
||||||
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
window.history.pushState({ type }, '', `#${type}`);
|
||||||
|
|
||||||
// Show cite
|
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
cite.hidden = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
Array.from(buttons).forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const type = button.getAttribute('data-source');
|
|
||||||
|
|
||||||
setSource(type);
|
|
||||||
|
|
||||||
if (historySupport) {
|
|
||||||
window.history.pushState({ type }, '', `#${type}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// List for backwards/forwards
|
|
||||||
window.addEventListener('popstate', event => {
|
|
||||||
if (event.state && Object.keys(event.state).includes('type')) {
|
|
||||||
setSource(event.state.type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there's no current type set, assume video
|
|
||||||
if (!hasInitialType) {
|
|
||||||
currentType = 'video';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace current history state
|
|
||||||
if (historySupport && types.includes(currentType)) {
|
|
||||||
window.history.replaceState({ type: currentType }, '', hasInitialType ? `#${currentType}` : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not video, load the source
|
|
||||||
if (currentType !== 'video') {
|
|
||||||
setSource(currentType, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(currentType);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// List for backwards/forwards
|
||||||
|
window.addEventListener('popstate', event => {
|
||||||
|
if (event.state && Object.keys(event.state).includes('type')) {
|
||||||
|
setSource(event.state.type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there's no current type set, assume video
|
||||||
|
if (!hasInitialType) {
|
||||||
|
currentType = 'video';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace current history state
|
||||||
|
if (historySupport && types.includes(currentType)) {
|
||||||
|
window.history.replaceState({ type: currentType }, '', hasInitialType ? `#${currentType}` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not video, load the source
|
||||||
|
if (currentType !== 'video') {
|
||||||
|
setSource(currentType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(currentType);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
+74
-74
@@ -1,78 +1,78 @@
|
|||||||
const sources = {
|
const sources = {
|
||||||
video: {
|
video: {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 576,
|
size: 576,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 720,
|
size: 720,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 1080,
|
size: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 1440,
|
size: 1440,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'English',
|
label: 'English',
|
||||||
srclang: 'en',
|
srclang: 'en',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'French',
|
label: 'French',
|
||||||
srclang: 'fr',
|
srclang: 'fr',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
audio: {
|
audio: {
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
title: 'Kishi Bashi – “It All Began With A Burst”',
|
title: 'Kishi Bashi – “It All Began With A Burst”',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
||||||
type: 'audio/mp3',
|
type: 'audio/mp3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
||||||
type: 'audio/ogg',
|
type: 'audio/ogg',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
youtube: {
|
youtube: {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
provider: 'youtube',
|
provider: 'youtube',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
vimeo: {
|
vimeo: {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://vimeo.com/40648169',
|
src: 'https://vimeo.com/40648169',
|
||||||
provider: 'vimeo',
|
provider: 'vimeo',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sources;
|
export default sources;
|
||||||
|
|||||||
+17
-17
@@ -4,28 +4,28 @@ const tabClassName = 'tab-focus';
|
|||||||
|
|
||||||
// Remove class on blur
|
// Remove class on blur
|
||||||
document.addEventListener('focusout', event => {
|
document.addEventListener('focusout', event => {
|
||||||
if (!event.target.classList || container.contains(event.target)) {
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.target.classList.remove(tabClassName);
|
event.target.classList.remove(tabClassName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Add classname to tabbed elements
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
if (event.keyCode !== 9) {
|
if (event.keyCode !== 9) {
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
focused.classList.add(tabClassName);
|
||||||
// This event fires before the focusin event
|
}, 10);
|
||||||
setTimeout(() => {
|
|
||||||
const focused = document.activeElement;
|
|
||||||
|
|
||||||
if (!focused || !focused.classList || container.contains(focused)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
focused.classList.add(tabClassName);
|
|
||||||
}, 10);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Toggle class on an element
|
// Toggle class on an element
|
||||||
const toggleClass = (element, className = '', toggle = false) =>
|
const toggleClass = (element, className = '', toggle = false) =>
|
||||||
element && element.classList[toggle ? 'add' : 'remove'](className);
|
element && element.classList[toggle ? 'add' : 'remove'](className);
|
||||||
|
|
||||||
export default toggleClass;
|
export default toggleClass;
|
||||||
|
|||||||
@@ -5,80 +5,80 @@
|
|||||||
// Shared
|
// Shared
|
||||||
.button,
|
.button,
|
||||||
.button__count {
|
.button__count {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: ($spacing-base * 0.75);
|
padding: ($spacing-base * 0.75);
|
||||||
position: relative;
|
position: relative;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.button {
|
.button {
|
||||||
background: $color-button-background;
|
background: $color-button-background;
|
||||||
color: $color-button-text;
|
color: $color-button-text;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
padding-left: ($spacing-base * 1.25);
|
padding-left: ($spacing-base * 1.25);
|
||||||
padding-right: ($spacing-base * 1.25);
|
padding-right: ($spacing-base * 1.25);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $color-button-background-hover;
|
background: $color-button-background-hover;
|
||||||
|
|
||||||
// Remove the underline/border
|
// Remove the underline/border
|
||||||
&::after {
|
&::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.tab-focus {
|
&.tab-focus {
|
||||||
@include tab-focus();
|
@include tab-focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button group
|
// Button group
|
||||||
.button--with-count {
|
.button--with-count {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
.button .icon {
|
.button .icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count bubble
|
// Count bubble
|
||||||
.button__count {
|
.button__count {
|
||||||
animation: fadein 0.2s ease;
|
animation: fadein 0.2s ease;
|
||||||
background: $color-button-count-background;
|
background: $color-button-count-background;
|
||||||
color: $color-button-count-text;
|
color: $color-button-count-text;
|
||||||
margin-left: ($spacing-base * 0.75);
|
margin-left: ($spacing-base * 0.75);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border: $arrow-size solid transparent;
|
border: $arrow-size solid transparent;
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-right-color: $color-button-count-background;
|
border-right-color: $color-button-count-background;
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,28 +3,28 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding-bottom: $spacing-base;
|
padding-bottom: $spacing-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h1 span {
|
h1 span {
|
||||||
animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
|
animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: $font-weight-light;
|
font-weight: $font-weight-light;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-to-action {
|
.call-to-action {
|
||||||
margin-top: ($spacing-base * 1.5);
|
margin-top: ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: $screen-md) {
|
@media only screen and (min-width: $screen-md) {
|
||||||
margin-right: ($spacing-base * 3);
|
margin-right: ($spacing-base * 3);
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
padding-bottom: ($spacing-base * 2);
|
padding-bottom: ($spacing-base * 2);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
p:first-of-type {
|
p:first-of-type {
|
||||||
@include font-size($font-size-base + 1);
|
@include font-size($font-size-base + 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
|
|
||||||
// Base size icon styles
|
// Base size icon styles
|
||||||
.icon {
|
.icon {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
height: $icon-size;
|
height: $icon-size;
|
||||||
vertical-align: -3px;
|
vertical-align: -3px;
|
||||||
width: $icon-size;
|
width: $icon-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Within elements
|
// Within elements
|
||||||
a svg,
|
a svg,
|
||||||
button svg,
|
button svg,
|
||||||
label svg {
|
label svg {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a .icon,
|
a .icon,
|
||||||
.btn .icon {
|
.btn .icon {
|
||||||
margin-right: ($spacing-base / 2);
|
margin-right: ($spacing-base / 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,45 +4,45 @@
|
|||||||
|
|
||||||
// Make a <button> look like an <a>
|
// Make a <button> look like an <a>
|
||||||
button.faux-link {
|
button.faux-link {
|
||||||
@extend a; // stylelint-disable-line
|
@extend a; // stylelint-disable-line
|
||||||
@include cancel-button-styles();
|
@include cancel-button-styles();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
a {
|
a {
|
||||||
border-bottom: 1px dotted currentColor;
|
border-bottom: 1px dotted currentColor;
|
||||||
color: $color-link;
|
color: $color-link;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: currentColor;
|
||||||
|
content: '';
|
||||||
|
height: 1px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: currentColor;
|
width: 100%;
|
||||||
content: '';
|
|
||||||
height: 1px;
|
|
||||||
left: 50%;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&.tab-focus {
|
||||||
&:focus {
|
@include tab-focus();
|
||||||
border-bottom-color: transparent;
|
}
|
||||||
outline: 0;
|
|
||||||
|
|
||||||
&::after {
|
&.no-border::after {
|
||||||
width: 100%;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.tab-focus {
|
|
||||||
@include tab-focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-border::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Lists
|
// Lists
|
||||||
ul,
|
ul,
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
img,
|
img,
|
||||||
video,
|
video,
|
||||||
audio {
|
audio {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: $spacing-base;
|
margin-bottom: $spacing-base;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,33 @@
|
|||||||
|
|
||||||
// Example players
|
// Example players
|
||||||
.plyr {
|
.plyr {
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
||||||
margin: $spacing-base auto;
|
margin: $spacing-base auto;
|
||||||
|
|
||||||
&.plyr--audio {
|
&.plyr--audio {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__video-wrapper::after {
|
.plyr__video-wrapper::after {
|
||||||
border: 1px solid rgba(#000, 0.15);
|
border: 1px solid rgba(#000, 0.15);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
left: 0;
|
left: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style full supported player
|
// Style full supported player
|
||||||
.plyr__cite {
|
.plyr__cite {
|
||||||
color: $color-gray-500;
|
color: $color-gray-500;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: ceil($spacing-base / 6);
|
margin-right: ceil($spacing-base / 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,60 +4,60 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: $page-background;
|
background: $page-background;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding-bottom: 1px; // Collapsing margins
|
padding-bottom: 1px; // Collapsing margins
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: $color-twitter;
|
fill: $color-twitter;
|
||||||
margin-right: ($spacing-base / 2);
|
margin-right: ($spacing-base / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $color-twitter;
|
color: $color-twitter;
|
||||||
|
|
||||||
&.tab-focus {
|
&.tab-focus {
|
||||||
@include tab-focus($color-twitter);
|
@include tab-focus($color-twitter);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,26 +5,26 @@
|
|||||||
// Error page
|
// Error page
|
||||||
html.error,
|
html.error,
|
||||||
.error body {
|
.error body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.error {
|
html.error {
|
||||||
background: $page-background;
|
background: $page-background;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error body {
|
.error body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error main {
|
.error main {
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@include font-size($font-size-large);
|
@include font-size($font-size-large);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
|
|
||||||
@media only screen and (min-width: $screen-md) {
|
@media only screen and (min-width: $screen-md) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: $container-max-width;
|
max-width: $container-max-width;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,24 @@
|
|||||||
|
|
||||||
// Fade
|
// Fade
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shrinkHide {
|
@keyframes shrinkHide {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
width: 38px;
|
width: 38px;
|
||||||
}
|
}
|
||||||
20% {
|
20% {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,46 +3,46 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-light;
|
font-weight: $font-weight-light;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-regular;
|
font-weight: $font-weight-regular;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-black;
|
font-weight: $font-weight-black;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,50 +5,50 @@
|
|||||||
// Convert a <button> into an <a>
|
// Convert a <button> into an <a>
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin cancel-button-styles() {
|
@mixin cancel-button-styles() {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
text-shadow: inherit;
|
text-shadow: inherit;
|
||||||
-moz-user-select: text; // stylelint-disable-line
|
-moz-user-select: text; // stylelint-disable-line
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nicer focus styles
|
// Nicer focus styles
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin tab-focus($color: $tab-focus-default-color) {
|
@mixin tab-focus($color: $tab-focus-default-color) {
|
||||||
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use rems for font sizing
|
// Use rems for font sizing
|
||||||
// Leave <body> at 100%/16px
|
// Leave <body> at 100%/16px
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@function calculate-rem($size) {
|
@function calculate-rem($size) {
|
||||||
$rem: $size / 16;
|
$rem: $size / 16;
|
||||||
@return #{$rem}rem;
|
@return #{$rem}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin font-size($size: $font-size-base) {
|
@mixin font-size($size: $font-size-base) {
|
||||||
font-size: $size * 1px; // Fallback in px
|
font-size: $size * 1px; // Fallback in px
|
||||||
font-size: calculate-rem($size);
|
font-size: calculate-rem($size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Font smoothing
|
// Font smoothing
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin font-smoothing($enabled: true) {
|
@mixin font-smoothing($enabled: true) {
|
||||||
@if $enabled {
|
@if $enabled {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
} @else {
|
} @else {
|
||||||
-moz-osx-font-smoothing: auto;
|
-moz-osx-font-smoothing: auto;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+74
-74
@@ -10,9 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
html {
|
html {
|
||||||
line-height: 1.15; /* 1 */
|
line-height: 1.15; /* 1 */
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
-ms-text-size-adjust: 100%; /* 2 */
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sections
|
/* Sections
|
||||||
@@ -23,7 +23,7 @@ html {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +36,7 @@ footer,
|
|||||||
header,
|
header,
|
||||||
nav,
|
nav,
|
||||||
section {
|
section {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,8 +45,8 @@ section {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin: 0.67em 0;
|
margin: 0.67em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grouping content
|
/* Grouping content
|
||||||
@@ -60,8 +60,8 @@ h1 {
|
|||||||
figcaption,
|
figcaption,
|
||||||
figure,
|
figure,
|
||||||
main {
|
main {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +69,7 @@ main {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
margin: 1em 40px;
|
margin: 1em 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,9 +78,9 @@ figure {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
box-sizing: content-box; /* 1 */
|
box-sizing: content-box; /* 1 */
|
||||||
height: 0; /* 1 */
|
height: 0; /* 1 */
|
||||||
overflow: visible; /* 2 */
|
overflow: visible; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,8 +89,8 @@ hr {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: monospace, monospace; /* 1 */
|
font-family: monospace, monospace; /* 1 */
|
||||||
font-size: 1em; /* 2 */
|
font-size: 1em; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Text-level semantics
|
/* Text-level semantics
|
||||||
@@ -102,8 +102,8 @@ pre {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
a {
|
a {
|
||||||
background-color: transparent; /* 1 */
|
background-color: transparent; /* 1 */
|
||||||
-webkit-text-decoration-skip: objects; /* 2 */
|
-webkit-text-decoration-skip: objects; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,9 +112,9 @@ a {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
abbr[title] {
|
abbr[title] {
|
||||||
border-bottom: none; /* 1 */
|
border-bottom: none; /* 1 */
|
||||||
text-decoration: underline; /* 2 */
|
text-decoration: underline; /* 2 */
|
||||||
text-decoration: underline dotted; /* 2 */
|
text-decoration: underline dotted; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +123,7 @@ abbr[title] {
|
|||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +132,7 @@ strong {
|
|||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,8 +143,8 @@ strong {
|
|||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
samp {
|
samp {
|
||||||
font-family: monospace, monospace; /* 1 */
|
font-family: monospace, monospace; /* 1 */
|
||||||
font-size: 1em; /* 2 */
|
font-size: 1em; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +152,7 @@ samp {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
dfn {
|
dfn {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,8 +160,8 @@ dfn {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mark {
|
mark {
|
||||||
background-color: #ff0;
|
background-color: #ff0;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,7 +169,7 @@ mark {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,18 +179,18 @@ small {
|
|||||||
|
|
||||||
sub,
|
sub,
|
||||||
sup {
|
sup {
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub {
|
sub {
|
||||||
bottom: -0.25em;
|
bottom: -0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
sup {
|
sup {
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Embedded content
|
/* Embedded content
|
||||||
@@ -202,7 +202,7 @@ sup {
|
|||||||
|
|
||||||
audio,
|
audio,
|
||||||
video {
|
video {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,8 +210,8 @@ video {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
audio:not([controls]) {
|
audio:not([controls]) {
|
||||||
display: none;
|
display: none;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +219,7 @@ audio:not([controls]) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,7 +227,7 @@ img {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
svg:not(:root) {
|
svg:not(:root) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forms
|
/* Forms
|
||||||
@@ -243,10 +243,10 @@ input,
|
|||||||
optgroup,
|
optgroup,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
font-family: sans-serif; /* 1 */
|
font-family: sans-serif; /* 1 */
|
||||||
font-size: 100%; /* 1 */
|
font-size: 100%; /* 1 */
|
||||||
line-height: 1.15; /* 1 */
|
line-height: 1.15; /* 1 */
|
||||||
margin: 0; /* 2 */
|
margin: 0; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,8 +256,8 @@ textarea {
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
input {
|
input {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,8 +267,8 @@ input {
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
select {
|
select {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,7 +281,7 @@ button,
|
|||||||
html [type='button'],
|
html [type='button'],
|
||||||
[type='reset'],
|
[type='reset'],
|
||||||
[type='submit'] {
|
[type='submit'] {
|
||||||
-webkit-appearance: button; /* 2 */
|
-webkit-appearance: button; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,8 +292,8 @@ button::-moz-focus-inner,
|
|||||||
[type='button']::-moz-focus-inner,
|
[type='button']::-moz-focus-inner,
|
||||||
[type='reset']::-moz-focus-inner,
|
[type='reset']::-moz-focus-inner,
|
||||||
[type='submit']::-moz-focus-inner {
|
[type='submit']::-moz-focus-inner {
|
||||||
border-style: none;
|
border-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +304,7 @@ button:-moz-focusring,
|
|||||||
[type='button']:-moz-focusring,
|
[type='button']:-moz-focusring,
|
||||||
[type='reset']:-moz-focusring,
|
[type='reset']:-moz-focusring,
|
||||||
[type='submit']:-moz-focusring {
|
[type='submit']:-moz-focusring {
|
||||||
outline: 1px dotted ButtonText;
|
outline: 1px dotted ButtonText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +312,7 @@ button:-moz-focusring,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
padding: 0.35em 0.75em 0.625em;
|
padding: 0.35em 0.75em 0.625em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,12 +323,12 @@ fieldset {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
box-sizing: border-box; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
color: inherit; /* 2 */
|
color: inherit; /* 2 */
|
||||||
display: table; /* 1 */
|
display: table; /* 1 */
|
||||||
max-width: 100%; /* 1 */
|
max-width: 100%; /* 1 */
|
||||||
padding: 0; /* 3 */
|
padding: 0; /* 3 */
|
||||||
white-space: normal; /* 1 */
|
white-space: normal; /* 1 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,8 +337,8 @@ legend {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
progress {
|
progress {
|
||||||
display: inline-block; /* 1 */
|
display: inline-block; /* 1 */
|
||||||
vertical-align: baseline; /* 2 */
|
vertical-align: baseline; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,7 +346,7 @@ progress {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,8 +356,8 @@ textarea {
|
|||||||
|
|
||||||
[type='checkbox'],
|
[type='checkbox'],
|
||||||
[type='radio'] {
|
[type='radio'] {
|
||||||
box-sizing: border-box; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
padding: 0; /* 2 */
|
padding: 0; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -366,7 +366,7 @@ textarea {
|
|||||||
|
|
||||||
[type='number']::-webkit-inner-spin-button,
|
[type='number']::-webkit-inner-spin-button,
|
||||||
[type='number']::-webkit-outer-spin-button {
|
[type='number']::-webkit-outer-spin-button {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -375,8 +375,8 @@ textarea {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[type='search'] {
|
[type='search'] {
|
||||||
-webkit-appearance: textfield; /* 1 */
|
-webkit-appearance: textfield; /* 1 */
|
||||||
outline-offset: -2px; /* 2 */
|
outline-offset: -2px; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,7 +385,7 @@ textarea {
|
|||||||
|
|
||||||
[type='search']::-webkit-search-cancel-button,
|
[type='search']::-webkit-search-cancel-button,
|
||||||
[type='search']::-webkit-search-decoration {
|
[type='search']::-webkit-search-decoration {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,8 +394,8 @@ textarea {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
::-webkit-file-upload-button {
|
||||||
-webkit-appearance: button; /* 1 */
|
-webkit-appearance: button; /* 1 */
|
||||||
font: inherit; /* 2 */
|
font: inherit; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Interactive
|
/* Interactive
|
||||||
@@ -408,7 +408,7 @@ textarea {
|
|||||||
|
|
||||||
details,
|
details,
|
||||||
menu {
|
menu {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -416,7 +416,7 @@ menu {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
summary {
|
summary {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scripting
|
/* Scripting
|
||||||
@@ -427,7 +427,7 @@ summary {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -435,7 +435,7 @@ canvas {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
template {
|
template {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hidden
|
/* Hidden
|
||||||
@@ -446,5 +446,5 @@ template {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
*,
|
*,
|
||||||
*::after,
|
*::after,
|
||||||
*::before {
|
*::before {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,17 @@
|
|||||||
// Plyr Settings
|
// Plyr Settings
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Font
|
// Font sizes
|
||||||
$plyr-font-family: inherit;
|
|
||||||
|
|
||||||
// Sizes
|
|
||||||
$plyr-font-size-base: 13px;
|
$plyr-font-size-base: 13px;
|
||||||
$plyr-font-size-small: 12px;
|
$plyr-font-size-small: 12px;
|
||||||
$plyr-font-size-time: 11px;
|
$plyr-font-size-time: 11px;
|
||||||
$plyr-font-size-badges: 9px;
|
$plyr-font-size-badges: 9px;
|
||||||
|
|
||||||
// Other
|
// Font weight
|
||||||
|
$plyr-font-weight-regular: 500;
|
||||||
|
$plyr-font-weight-bold: 600;
|
||||||
|
|
||||||
|
// Font smoothing
|
||||||
$plyr-font-smoothing: true;
|
$plyr-font-smoothing: true;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
'Segoe UI Symbol';
|
'Segoe UI Symbol';
|
||||||
|
|
||||||
$font-size-base: 15;
|
$font-size-base: 15;
|
||||||
$font-size-small: 13;
|
$font-size-small: 13;
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
|
|
||||||
// Set to 100% for rem sizing
|
// Set to 100% for rem sizing
|
||||||
html {
|
html {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@include font-smoothing();
|
@include font-smoothing();
|
||||||
@include font-size($font-size-base);
|
@include font-size($font-size-base);
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
font-family: $font-sans-serif;
|
font-family: $font-sans-serif;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
small {
|
small {
|
||||||
margin: 0 0 ($spacing-base * 1.5);
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
@include font-size($font-size-small);
|
@include font-size($font-size-small);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@include font-size($font-size-h1);
|
@include font-size($font-size-h1);
|
||||||
color: $color-headings;
|
color: $color-headings;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
letter-spacing: $letter-spacing-headings;
|
letter-spacing: $letter-spacing-headings;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
margin: 0 0 ($spacing-base * 1.5);
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.no-border {
|
.no-border {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide only visually, but have it available for screen readers: h5bp.com/v
|
// Hide only visually, but have it available for screen readers: h5bp.com/v
|
||||||
.sr-only {
|
.sr-only {
|
||||||
border: 0;
|
border: 0;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin: -1px;
|
margin: -1px;
|
||||||
opacity: 0.001;
|
opacity: 0.001;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+70
-70
@@ -1334,19 +1334,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -2444,39 +2444,39 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -8900,41 +8900,41 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+70
-70
@@ -1328,19 +1328,19 @@ function getPercentage(current, max) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -2438,39 +2438,39 @@ var controls = {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -8894,41 +8894,41 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
|
|||||||
Vendored
+70
-70
@@ -7640,19 +7640,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -8759,39 +8759,39 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -15328,41 +15328,41 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+70
-70
@@ -7634,19 +7634,19 @@ function getPercentage(current, max) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -8753,39 +8753,39 @@ var controls = {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -15322,41 +15322,41 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
|
|||||||
+98
-98
@@ -1,100 +1,100 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.5.10",
|
"version": "3.5.10",
|
||||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"main": "dist/plyr.js",
|
"main": "dist/plyr.js",
|
||||||
"types": "src/js/plyr.d.ts",
|
"types": "src/js/plyr.d.ts",
|
||||||
"module": "dist/plyr.min.mjs",
|
"module": "dist/plyr.min.mjs",
|
||||||
"jsnext:main": "dist/plyr.min.mjs",
|
"jsnext:main": "dist/plyr.min.mjs",
|
||||||
"browser": "dist/plyr.min.js",
|
"browser": "dist/plyr.min.js",
|
||||||
"sass": "src/sass/plyr.scss",
|
"sass": "src/sass/plyr.scss",
|
||||||
"style": "dist/plyr.css",
|
"style": "dist/plyr.css",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"HTML5 Video",
|
"HTML5 Video",
|
||||||
"HTML5 Audio",
|
"HTML5 Audio",
|
||||||
"Media Player",
|
"Media Player",
|
||||||
"DASH",
|
"DASH",
|
||||||
"Shaka",
|
"Shaka",
|
||||||
"WordPress",
|
"WordPress",
|
||||||
"HLS"
|
"HLS"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/sampotts/plyr.git"
|
"url": "git://github.com/sampotts/plyr.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sampotts/plyr/issues"
|
"url": "https://github.com/sampotts/plyr/issues"
|
||||||
},
|
},
|
||||||
"browserslist": "> 1%",
|
"browserslist": "> 1%",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"lint": "eslint src/js && npm run-script remark",
|
"lint": "eslint src/js && npm run-script remark",
|
||||||
"lint:fix": "eslint --fix src/js",
|
"lint:fix": "eslint --fix src/js",
|
||||||
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
||||||
"deploy": "yarn lint && gulp version && gulp build && gulp deploy",
|
"deploy": "yarn lint && gulp version && gulp build && gulp deploy",
|
||||||
"format": "prettier --write ./src/js/**/*.js"
|
"format": "prettier --write \"./{src,demo/src}/**/*.{js,scss}\""
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"autoprefixer": "^9.7.5",
|
"autoprefixer": "^9.7.5",
|
||||||
"aws-sdk": "^2.648.0",
|
"aws-sdk": "^2.649.0",
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.9.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"browser-sync": "^2.26.7",
|
"browser-sync": "^2.26.7",
|
||||||
"del": "^5.1.0",
|
"del": "^5.1.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-airbnb-base": "^14.1.0",
|
"eslint-config-airbnb-base": "^14.1.0",
|
||||||
"eslint-config-prettier": "^6.10.1",
|
"eslint-config-prettier": "^6.10.1",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"eslint-plugin-simple-import-sort": "^5.0.2",
|
"eslint-plugin-simple-import-sort": "^5.0.2",
|
||||||
"fancy-log": "^1.3.3",
|
"fancy-log": "^1.3.3",
|
||||||
"fastly-purge": "^1.0.1",
|
"fastly-purge": "^1.0.1",
|
||||||
"git-branch": "^2.0.1",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-awspublish": "^4.1.1",
|
"gulp-awspublish": "^4.1.1",
|
||||||
"gulp-better-rollup": "^4.0.1",
|
"gulp-better-rollup": "^4.0.1",
|
||||||
"gulp-filter": "^6.0.0",
|
"gulp-filter": "^6.0.0",
|
||||||
"gulp-header": "^2.0.9",
|
"gulp-header": "^2.0.9",
|
||||||
"gulp-hub": "^4.2.0",
|
"gulp-hub": "^4.2.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-open": "^3.0.1",
|
"gulp-open": "^3.0.1",
|
||||||
"gulp-plumber": "^1.2.1",
|
"gulp-plumber": "^1.2.1",
|
||||||
"gulp-postcss": "^8.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-replace": "^1.0.0",
|
"gulp-replace": "^1.0.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-sourcemaps": "^2.6.5",
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
"gulp-svgstore": "^7.0.1",
|
"gulp-svgstore": "^7.0.1",
|
||||||
"gulp-terser": "^1.2.0",
|
"gulp-terser": "^1.2.0",
|
||||||
"postcss-clean": "^1.1.0",
|
"postcss-clean": "^1.1.0",
|
||||||
"postcss-custom-properties": "^9.1.1",
|
"postcss-custom-properties": "^9.1.1",
|
||||||
"prettier-eslint": "^9.0.1",
|
"prettier-eslint": "^9.0.1",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"remark-cli": "^7.0.1",
|
"remark-cli": "^8.0.0",
|
||||||
"remark-validate-links": "^10.0.0",
|
"remark-validate-links": "^10.0.0",
|
||||||
"rollup": "^2.2.0",
|
"rollup": "^2.3.1",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"stylelint": "^13.2.1",
|
"stylelint": "^13.2.1",
|
||||||
"stylelint-config-prettier": "^8.0.1",
|
"stylelint-config-prettier": "^8.0.1",
|
||||||
"stylelint-config-recommended": "^3.0.0",
|
"stylelint-config-recommended": "^3.0.0",
|
||||||
"stylelint-config-sass-guidelines": "^7.0.0",
|
"stylelint-config-sass-guidelines": "^7.0.0",
|
||||||
"stylelint-order": "^4.0.0",
|
"stylelint-order": "^4.0.0",
|
||||||
"stylelint-scss": "^3.16.0",
|
"stylelint-scss": "^3.16.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.1.0",
|
"stylelint-selector-bem-pattern": "^2.1.0",
|
||||||
"through2": "^3.0.1"
|
"through2": "^3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"loadjs": "^4.2.0",
|
"loadjs": "^4.2.0",
|
||||||
"rangetouch": "^2.0.1",
|
"rangetouch": "^2.0.1",
|
||||||
"url-polyfill": "^1.1.8"
|
"url-polyfill": "^1.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+314
-314
@@ -8,12 +8,12 @@ import support from './support';
|
|||||||
import { dedupe } from './utils/arrays';
|
import { dedupe } from './utils/arrays';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
emptyElement,
|
emptyElement,
|
||||||
getAttributesFromSelector,
|
getAttributesFromSelector,
|
||||||
insertAfter,
|
insertAfter,
|
||||||
removeElement,
|
removeElement,
|
||||||
toggleClass,
|
toggleClass,
|
||||||
} from './utils/elements';
|
} from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
import fetch from './utils/fetch';
|
import fetch from './utils/fetch';
|
||||||
@@ -23,371 +23,371 @@ import { getHTML } from './utils/strings';
|
|||||||
import { parseUrl } from './utils/urls';
|
import { parseUrl } from './utils/urls';
|
||||||
|
|
||||||
const captions = {
|
const captions = {
|
||||||
// Setup captions
|
// Setup captions
|
||||||
setup() {
|
setup() {
|
||||||
// Requires UI support
|
// Requires UI support
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only Vimeo and HTML5 video supported at this point
|
// Only Vimeo and HTML5 video supported at this point
|
||||||
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
||||||
// Clear menu and hide
|
// Clear menu and hide
|
||||||
if (
|
if (
|
||||||
is.array(this.config.controls) &&
|
is.array(this.config.controls) &&
|
||||||
this.config.controls.includes('settings') &&
|
this.config.controls.includes('settings') &&
|
||||||
this.config.settings.includes('captions')
|
this.config.settings.includes('captions')
|
||||||
) {
|
) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the container
|
// Inject the container
|
||||||
if (!is.element(this.elements.captions)) {
|
if (!is.element(this.elements.captions)) {
|
||||||
this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));
|
this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));
|
||||||
|
|
||||||
insertAfter(this.elements.captions, this.elements.wrapper);
|
insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix IE captions if CORS is used
|
// Fix IE captions if CORS is used
|
||||||
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
||||||
if (browser.isIE && window.URL) {
|
if (browser.isIE && window.URL) {
|
||||||
const elements = this.media.querySelectorAll('track');
|
const elements = this.media.querySelectorAll('track');
|
||||||
|
|
||||||
Array.from(elements).forEach(track => {
|
Array.from(elements).forEach(track => {
|
||||||
const src = track.getAttribute('src');
|
const src = track.getAttribute('src');
|
||||||
const url = parseUrl(src);
|
const url = parseUrl(src);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
url !== null &&
|
url !== null &&
|
||||||
url.hostname !== window.location.href.hostname &&
|
url.hostname !== window.location.href.hostname &&
|
||||||
['http:', 'https:'].includes(url.protocol)
|
['http:', 'https:'].includes(url.protocol)
|
||||||
) {
|
) {
|
||||||
fetch(src, 'blob')
|
fetch(src, 'blob')
|
||||||
.then(blob => {
|
.then(blob => {
|
||||||
track.setAttribute('src', window.URL.createObjectURL(blob));
|
track.setAttribute('src', window.URL.createObjectURL(blob));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
removeElement(track);
|
removeElement(track);
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get and set initial data
|
// Get and set initial data
|
||||||
// The "preferred" options are not realized unless / until the wanted language has a match
|
// The "preferred" options are not realized unless / until the wanted language has a match
|
||||||
// * languages: Array of user's browser languages.
|
// * languages: Array of user's browser languages.
|
||||||
// * language: The language preferred by user settings or config
|
// * language: The language preferred by user settings or config
|
||||||
// * active: The state preferred by user settings or config
|
// * active: The state preferred by user settings or config
|
||||||
// * toggled: The real captions state
|
// * toggled: The real captions state
|
||||||
|
|
||||||
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
||||||
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
||||||
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
||||||
|
|
||||||
// Use first browser language when language is 'auto'
|
// Use first browser language when language is 'auto'
|
||||||
if (language === 'auto') {
|
if (language === 'auto') {
|
||||||
[language] = languages;
|
[language] = languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = this.storage.get('captions');
|
let active = this.storage.get('captions');
|
||||||
if (!is.boolean(active)) {
|
if (!is.boolean(active)) {
|
||||||
({ active } = this.config.captions);
|
({ active } = this.config.captions);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.captions, {
|
Object.assign(this.captions, {
|
||||||
toggled: false,
|
toggled: false,
|
||||||
active,
|
active,
|
||||||
language,
|
language,
|
||||||
languages,
|
languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch changes to textTracks and update captions menu
|
||||||
|
if (this.isHTML5) {
|
||||||
|
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
|
||||||
|
on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
||||||
|
setTimeout(captions.update.bind(this), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update available language options in settings based on tracks
|
||||||
|
update() {
|
||||||
|
const tracks = captions.getTracks.call(this, true);
|
||||||
|
// Get the wanted language
|
||||||
|
const { active, language, meta, currentTrackNode } = this.captions;
|
||||||
|
const languageExists = Boolean(tracks.find(track => track.language === language));
|
||||||
|
|
||||||
|
// Handle tracks (add event listener and "pseudo"-default)
|
||||||
|
if (this.isHTML5 && this.isVideo) {
|
||||||
|
tracks
|
||||||
|
.filter(track => !meta.get(track))
|
||||||
|
.forEach(track => {
|
||||||
|
this.debug.log('Track added', track);
|
||||||
|
// Attempt to store if the original dom element was "default"
|
||||||
|
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
|
||||||
|
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Watch changes to textTracks and update captions menu
|
// Update language first time it matches, or if the previous matching track was removed
|
||||||
if (this.isHTML5) {
|
if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
|
||||||
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
|
captions.setLanguage.call(this, language);
|
||||||
on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
|
captions.toggle.call(this, active && languageExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
// Enable or disable captions based on track length
|
||||||
setTimeout(captions.update.bind(this), 0);
|
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
||||||
},
|
|
||||||
|
|
||||||
// Update available language options in settings based on tracks
|
// Update available languages in list
|
||||||
update() {
|
if (
|
||||||
const tracks = captions.getTracks.call(this, true);
|
is.array(this.config.controls) &&
|
||||||
// Get the wanted language
|
this.config.controls.includes('settings') &&
|
||||||
const { active, language, meta, currentTrackNode } = this.captions;
|
this.config.settings.includes('captions')
|
||||||
const languageExists = Boolean(tracks.find(track => track.language === language));
|
) {
|
||||||
|
controls.setCaptionsMenu.call(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Handle tracks (add event listener and "pseudo"-default)
|
// Toggle captions display
|
||||||
if (this.isHTML5 && this.isVideo) {
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
tracks
|
toggle(input, passive = true) {
|
||||||
.filter(track => !meta.get(track))
|
// If there's no full support
|
||||||
.forEach(track => {
|
if (!this.supported.ui) {
|
||||||
this.debug.log('Track added', track);
|
return;
|
||||||
// Attempt to store if the original dom element was "default"
|
}
|
||||||
meta.set(track, {
|
|
||||||
default: track.mode === 'showing',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Turn off native caption rendering to avoid double captions
|
const { toggled } = this.captions; // Current state
|
||||||
// eslint-disable-next-line no-param-reassign
|
const activeClass = this.config.classNames.captions.active;
|
||||||
track.mode = 'hidden';
|
// Get the next state
|
||||||
|
// If the method is called without parameter, toggle based on current value
|
||||||
|
const active = is.nullOrUndefined(input) ? !toggled : input;
|
||||||
|
|
||||||
// Add event listener for cue changes
|
// Update state and trigger event
|
||||||
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
|
if (active !== toggled) {
|
||||||
});
|
// When passive, don't override user preferences
|
||||||
}
|
if (!passive) {
|
||||||
|
this.captions.active = active;
|
||||||
|
this.storage.set({ captions: active });
|
||||||
|
}
|
||||||
|
|
||||||
// Update language first time it matches, or if the previous matching track was removed
|
// Force language if the call isn't passive and there is no matching language to toggle to
|
||||||
if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
|
if (!this.language && active && !passive) {
|
||||||
captions.setLanguage.call(this, language);
|
|
||||||
captions.toggle.call(this, active && languageExists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable or disable captions based on track length
|
|
||||||
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
|
||||||
|
|
||||||
// Update available languages in list
|
|
||||||
if (
|
|
||||||
is.array(this.config.controls) &&
|
|
||||||
this.config.controls.includes('settings') &&
|
|
||||||
this.config.settings.includes('captions')
|
|
||||||
) {
|
|
||||||
controls.setCaptionsMenu.call(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Toggle captions display
|
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
|
||||||
toggle(input, passive = true) {
|
|
||||||
// If there's no full support
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { toggled } = this.captions; // Current state
|
|
||||||
const activeClass = this.config.classNames.captions.active;
|
|
||||||
// Get the next state
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
|
||||||
const active = is.nullOrUndefined(input) ? !toggled : input;
|
|
||||||
|
|
||||||
// Update state and trigger event
|
|
||||||
if (active !== toggled) {
|
|
||||||
// When passive, don't override user preferences
|
|
||||||
if (!passive) {
|
|
||||||
this.captions.active = active;
|
|
||||||
this.storage.set({ captions: active });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force language if the call isn't passive and there is no matching language to toggle to
|
|
||||||
if (!this.language && active && !passive) {
|
|
||||||
const tracks = captions.getTracks.call(this);
|
|
||||||
const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);
|
|
||||||
|
|
||||||
// Override user preferences to avoid switching languages if a matching track is added
|
|
||||||
this.captions.language = track.language;
|
|
||||||
|
|
||||||
// Set caption, but don't store in localStorage as user preference
|
|
||||||
captions.set.call(this, tracks.indexOf(track));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle button if it's enabled
|
|
||||||
if (this.elements.buttons.captions) {
|
|
||||||
this.elements.buttons.captions.pressed = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add class hook
|
|
||||||
toggleClass(this.elements.container, activeClass, active);
|
|
||||||
|
|
||||||
this.captions.toggled = active;
|
|
||||||
|
|
||||||
// Update settings menu
|
|
||||||
controls.updateSetting.call(this, 'captions');
|
|
||||||
|
|
||||||
// Trigger event (not used internally)
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set captions by track index
|
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
|
||||||
set(index, passive = true) {
|
|
||||||
const tracks = captions.getTracks.call(this);
|
const tracks = captions.getTracks.call(this);
|
||||||
|
const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);
|
||||||
|
|
||||||
// Disable captions if setting to -1
|
// Override user preferences to avoid switching languages if a matching track is added
|
||||||
if (index === -1) {
|
this.captions.language = track.language;
|
||||||
captions.toggle.call(this, false, passive);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is.number(index)) {
|
// Set caption, but don't store in localStorage as user preference
|
||||||
this.debug.warn('Invalid caption argument', index);
|
captions.set.call(this, tracks.indexOf(track));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(index in tracks)) {
|
// Toggle button if it's enabled
|
||||||
this.debug.warn('Track not found', index);
|
if (this.elements.buttons.captions) {
|
||||||
return;
|
this.elements.buttons.captions.pressed = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.captions.currentTrack !== index) {
|
// Add class hook
|
||||||
this.captions.currentTrack = index;
|
toggleClass(this.elements.container, activeClass, active);
|
||||||
const track = tracks[index];
|
|
||||||
const { language } = track || {};
|
|
||||||
|
|
||||||
// Store reference to node for invalidation on remove
|
this.captions.toggled = active;
|
||||||
this.captions.currentTrackNode = track;
|
|
||||||
|
|
||||||
// Update settings menu
|
// Update settings menu
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this, 'captions');
|
||||||
|
|
||||||
// When passive, don't override user preferences
|
// Trigger event (not used internally)
|
||||||
if (!passive) {
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
this.captions.language = language;
|
}
|
||||||
this.storage.set({ language });
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Vimeo captions
|
// Set captions by track index
|
||||||
if (this.isVimeo) {
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
this.embed.enableTextTrack(language);
|
set(index, passive = true) {
|
||||||
}
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
// Trigger event
|
// Disable captions if setting to -1
|
||||||
triggerEvent.call(this, this.media, 'languagechange');
|
if (index === -1) {
|
||||||
}
|
captions.toggle.call(this, false, passive);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Show captions
|
if (!is.number(index)) {
|
||||||
captions.toggle.call(this, true, passive);
|
this.debug.warn('Invalid caption argument', index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isHTML5 && this.isVideo) {
|
if (!(index in tracks)) {
|
||||||
// If we change the active track while a cue is already displayed we need to update it
|
this.debug.warn('Track not found', index);
|
||||||
captions.updateCues.call(this);
|
return;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Set captions by language
|
if (this.captions.currentTrack !== index) {
|
||||||
// Used internally for the language setter with the passive option forced to false
|
this.captions.currentTrack = index;
|
||||||
setLanguage(input, passive = true) {
|
const track = tracks[index];
|
||||||
if (!is.string(input)) {
|
const { language } = track || {};
|
||||||
this.debug.warn('Invalid language argument', input);
|
|
||||||
return;
|
// Store reference to node for invalidation on remove
|
||||||
}
|
this.captions.currentTrackNode = track;
|
||||||
// Normalize
|
|
||||||
const language = input.toLowerCase();
|
// Update settings menu
|
||||||
|
controls.updateSetting.call(this, 'captions');
|
||||||
|
|
||||||
|
// When passive, don't override user preferences
|
||||||
|
if (!passive) {
|
||||||
this.captions.language = language;
|
this.captions.language = language;
|
||||||
|
this.storage.set({ language });
|
||||||
|
}
|
||||||
|
|
||||||
// Set currentTrack
|
// Handle Vimeo captions
|
||||||
const tracks = captions.getTracks.call(this);
|
if (this.isVimeo) {
|
||||||
const track = captions.findTrack.call(this, [language]);
|
this.embed.enableTextTrack(language);
|
||||||
captions.set.call(this, tracks.indexOf(track), passive);
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Get current valid caption tracks
|
// Trigger event
|
||||||
// If update is false it will also ignore tracks without metadata
|
triggerEvent.call(this, this.media, 'languagechange');
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
}
|
||||||
getTracks(update = false) {
|
|
||||||
// Handle media or textTracks missing or null
|
|
||||||
const tracks = Array.from((this.media || {}).textTracks || []);
|
|
||||||
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
|
|
||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
|
||||||
return tracks
|
|
||||||
.filter(track => !this.isHTML5 || update || this.captions.meta.has(track))
|
|
||||||
.filter(track => ['captions', 'subtitles'].includes(track.kind));
|
|
||||||
},
|
|
||||||
|
|
||||||
// Match tracks based on languages and get the first
|
// Show captions
|
||||||
findTrack(languages, force = false) {
|
captions.toggle.call(this, true, passive);
|
||||||
const tracks = captions.getTracks.call(this);
|
|
||||||
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
|
||||||
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
|
||||||
let track;
|
|
||||||
|
|
||||||
languages.every(language => {
|
if (this.isHTML5 && this.isVideo) {
|
||||||
track = sorted.find(t => t.language === language);
|
// If we change the active track while a cue is already displayed we need to update it
|
||||||
return !track; // Break iteration if there is a match
|
captions.updateCues.call(this);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// If no match is found but is required, get first
|
// Set captions by language
|
||||||
return track || (force ? sorted[0] : undefined);
|
// Used internally for the language setter with the passive option forced to false
|
||||||
},
|
setLanguage(input, passive = true) {
|
||||||
|
if (!is.string(input)) {
|
||||||
|
this.debug.warn('Invalid language argument', input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Normalize
|
||||||
|
const language = input.toLowerCase();
|
||||||
|
this.captions.language = language;
|
||||||
|
|
||||||
// Get the current track
|
// Set currentTrack
|
||||||
getCurrentTrack() {
|
const tracks = captions.getTracks.call(this);
|
||||||
return captions.getTracks.call(this)[this.currentTrack];
|
const track = captions.findTrack.call(this, [language]);
|
||||||
},
|
captions.set.call(this, tracks.indexOf(track), passive);
|
||||||
|
},
|
||||||
|
|
||||||
// Get UI label for track
|
// Get current valid caption tracks
|
||||||
getLabel(track) {
|
// If update is false it will also ignore tracks without metadata
|
||||||
let currentTrack = track;
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
|
getTracks(update = false) {
|
||||||
|
// Handle media or textTracks missing or null
|
||||||
|
const tracks = Array.from((this.media || {}).textTracks || []);
|
||||||
|
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
|
||||||
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
return tracks
|
||||||
|
.filter(track => !this.isHTML5 || update || this.captions.meta.has(track))
|
||||||
|
.filter(track => ['captions', 'subtitles'].includes(track.kind));
|
||||||
|
},
|
||||||
|
|
||||||
if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {
|
// Match tracks based on languages and get the first
|
||||||
currentTrack = captions.getCurrentTrack.call(this);
|
findTrack(languages, force = false) {
|
||||||
}
|
const tracks = captions.getTracks.call(this);
|
||||||
|
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
||||||
|
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
||||||
|
let track;
|
||||||
|
|
||||||
if (is.track(currentTrack)) {
|
languages.every(language => {
|
||||||
if (!is.empty(currentTrack.label)) {
|
track = sorted.find(t => t.language === language);
|
||||||
return currentTrack.label;
|
return !track; // Break iteration if there is a match
|
||||||
}
|
});
|
||||||
|
|
||||||
if (!is.empty(currentTrack.language)) {
|
// If no match is found but is required, get first
|
||||||
return track.language.toUpperCase();
|
return track || (force ? sorted[0] : undefined);
|
||||||
}
|
},
|
||||||
|
|
||||||
return i18n.get('enabled', this.config);
|
// Get the current track
|
||||||
}
|
getCurrentTrack() {
|
||||||
|
return captions.getTracks.call(this)[this.currentTrack];
|
||||||
|
},
|
||||||
|
|
||||||
return i18n.get('disabled', this.config);
|
// Get UI label for track
|
||||||
},
|
getLabel(track) {
|
||||||
|
let currentTrack = track;
|
||||||
|
|
||||||
// Update captions using current track's active cues
|
if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {
|
||||||
// Also optional array argument in case there isn't any track (ex: vimeo)
|
currentTrack = captions.getCurrentTrack.call(this);
|
||||||
updateCues(input) {
|
}
|
||||||
// Requires UI
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is.element(this.elements.captions)) {
|
if (is.track(currentTrack)) {
|
||||||
this.debug.warn('No captions element to render to');
|
if (!is.empty(currentTrack.label)) {
|
||||||
return;
|
return currentTrack.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only accept array or empty input
|
if (!is.empty(currentTrack.language)) {
|
||||||
if (!is.nullOrUndefined(input) && !Array.isArray(input)) {
|
return track.language.toUpperCase();
|
||||||
this.debug.warn('updateCues: Invalid input', input);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cues = input;
|
return i18n.get('enabled', this.config);
|
||||||
|
}
|
||||||
|
|
||||||
// Get cues from track
|
return i18n.get('disabled', this.config);
|
||||||
if (!cues) {
|
},
|
||||||
const track = captions.getCurrentTrack.call(this);
|
|
||||||
|
|
||||||
cues = Array.from((track || {}).activeCues || [])
|
// Update captions using current track's active cues
|
||||||
.map(cue => cue.getCueAsHTML())
|
// Also optional array argument in case there isn't any track (ex: vimeo)
|
||||||
.map(getHTML);
|
updateCues(input) {
|
||||||
}
|
// Requires UI
|
||||||
|
if (!this.supported.ui) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set new caption text
|
if (!is.element(this.elements.captions)) {
|
||||||
const content = cues.map(cueText => cueText.trim()).join('\n');
|
this.debug.warn('No captions element to render to');
|
||||||
const changed = content !== this.elements.captions.innerHTML;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
// Only accept array or empty input
|
||||||
// Empty the container and create a new child element
|
if (!is.nullOrUndefined(input) && !Array.isArray(input)) {
|
||||||
emptyElement(this.elements.captions);
|
this.debug.warn('updateCues: Invalid input', input);
|
||||||
const caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));
|
return;
|
||||||
caption.innerHTML = content;
|
}
|
||||||
this.elements.captions.appendChild(caption);
|
|
||||||
|
|
||||||
// Trigger event
|
let cues = input;
|
||||||
triggerEvent.call(this, this.media, 'cuechange');
|
|
||||||
}
|
// Get cues from track
|
||||||
},
|
if (!cues) {
|
||||||
|
const track = captions.getCurrentTrack.call(this);
|
||||||
|
|
||||||
|
cues = Array.from((track || {}).activeCues || [])
|
||||||
|
.map(cue => cue.getCueAsHTML())
|
||||||
|
.map(getHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new caption text
|
||||||
|
const content = cues.map(cueText => cueText.trim()).join('\n');
|
||||||
|
const changed = content !== this.elements.captions.innerHTML;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
// Empty the container and create a new child element
|
||||||
|
emptyElement(this.elements.captions);
|
||||||
|
const caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));
|
||||||
|
caption.innerHTML = content;
|
||||||
|
this.elements.captions.appendChild(caption);
|
||||||
|
|
||||||
|
// Trigger event
|
||||||
|
triggerEvent.call(this, this.media, 'cuechange');
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default captions;
|
export default captions;
|
||||||
|
|||||||
+422
-422
@@ -3,437 +3,437 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
// Disable
|
// Disable
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
// Custom media title
|
||||||
|
title: '',
|
||||||
|
|
||||||
|
// Logging to console
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
// Auto play (if supported)
|
||||||
|
autoplay: false,
|
||||||
|
|
||||||
|
// Only allow one media playing at once (vimeo only)
|
||||||
|
autopause: true,
|
||||||
|
|
||||||
|
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
|
||||||
|
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
|
||||||
|
playsinline: true,
|
||||||
|
|
||||||
|
// Default time to skip when rewind/fast forward
|
||||||
|
seekTime: 10,
|
||||||
|
|
||||||
|
// Default volume
|
||||||
|
volume: 1,
|
||||||
|
muted: false,
|
||||||
|
|
||||||
|
// Pass a custom duration
|
||||||
|
duration: null,
|
||||||
|
|
||||||
|
// Display the media duration on load in the current time position
|
||||||
|
// If you have opted to display both duration and currentTime, this is ignored
|
||||||
|
displayDuration: true,
|
||||||
|
|
||||||
|
// Invert the current time to be a countdown
|
||||||
|
invertTime: true,
|
||||||
|
|
||||||
|
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
||||||
|
toggleInvert: true,
|
||||||
|
|
||||||
|
// Force an aspect ratio
|
||||||
|
// The format must be `'w:h'` (e.g. `'16:9'`)
|
||||||
|
ratio: null,
|
||||||
|
|
||||||
|
// Click video container to play/pause
|
||||||
|
clickToPlay: true,
|
||||||
|
|
||||||
|
// Auto hide the controls
|
||||||
|
hideControls: true,
|
||||||
|
|
||||||
|
// Reset to start when playback ended
|
||||||
|
resetOnEnd: false,
|
||||||
|
|
||||||
|
// Disable the standard context menu
|
||||||
|
disableContextMenu: true,
|
||||||
|
|
||||||
|
// Sprite (for icons)
|
||||||
|
loadSprite: true,
|
||||||
|
iconPrefix: 'plyr',
|
||||||
|
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
||||||
|
|
||||||
|
// Blank video (used to prevent errors on source change)
|
||||||
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
|
|
||||||
|
// Quality default
|
||||||
|
quality: {
|
||||||
|
default: 576,
|
||||||
|
// The options to display in the UI, if available for the source media
|
||||||
|
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||||
|
forced: false,
|
||||||
|
onChange: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set loops
|
||||||
|
loop: {
|
||||||
|
active: false,
|
||||||
|
// start: null,
|
||||||
|
// end: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Speed default and options to display
|
||||||
|
speed: {
|
||||||
|
selected: 1,
|
||||||
|
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
||||||
|
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Keyboard shortcut settings
|
||||||
|
keyboard: {
|
||||||
|
focused: true,
|
||||||
|
global: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Display tooltips
|
||||||
|
tooltips: {
|
||||||
|
controls: false,
|
||||||
|
seek: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Captions settings
|
||||||
|
captions: {
|
||||||
|
active: false,
|
||||||
|
language: 'auto',
|
||||||
|
// Listen to new tracks added after Plyr is initialized.
|
||||||
|
// This is needed for streaming captions, but may result in unselectable options
|
||||||
|
update: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fullscreen settings
|
||||||
|
fullscreen: {
|
||||||
|
enabled: true, // Allow fullscreen?
|
||||||
|
fallback: true, // Fallback using full viewport/window
|
||||||
|
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Local storage
|
||||||
|
storage: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
key: 'plyr',
|
||||||
|
},
|
||||||
|
|
||||||
// Custom media title
|
// Default controls
|
||||||
title: '',
|
controls: [
|
||||||
|
'play-large',
|
||||||
|
// 'restart',
|
||||||
|
// 'rewind',
|
||||||
|
'play',
|
||||||
|
// 'fast-forward',
|
||||||
|
'progress',
|
||||||
|
'current-time',
|
||||||
|
// 'duration',
|
||||||
|
'mute',
|
||||||
|
'volume',
|
||||||
|
'captions',
|
||||||
|
'settings',
|
||||||
|
'pip',
|
||||||
|
'airplay',
|
||||||
|
// 'download',
|
||||||
|
'fullscreen',
|
||||||
|
],
|
||||||
|
settings: ['captions', 'quality', 'speed'],
|
||||||
|
|
||||||
// Logging to console
|
// Localisation
|
||||||
debug: false,
|
i18n: {
|
||||||
|
restart: 'Restart',
|
||||||
// Auto play (if supported)
|
rewind: 'Rewind {seektime}s',
|
||||||
autoplay: false,
|
play: 'Play',
|
||||||
|
pause: 'Pause',
|
||||||
// Only allow one media playing at once (vimeo only)
|
fastForward: 'Forward {seektime}s',
|
||||||
autopause: true,
|
seek: 'Seek',
|
||||||
|
seekLabel: '{currentTime} of {duration}',
|
||||||
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
|
played: 'Played',
|
||||||
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
|
buffered: 'Buffered',
|
||||||
playsinline: true,
|
currentTime: 'Current time',
|
||||||
|
duration: 'Duration',
|
||||||
// Default time to skip when rewind/fast forward
|
volume: 'Volume',
|
||||||
seekTime: 10,
|
mute: 'Mute',
|
||||||
|
unmute: 'Unmute',
|
||||||
// Default volume
|
enableCaptions: 'Enable captions',
|
||||||
volume: 1,
|
disableCaptions: 'Disable captions',
|
||||||
muted: false,
|
download: 'Download',
|
||||||
|
enterFullscreen: 'Enter fullscreen',
|
||||||
// Pass a custom duration
|
exitFullscreen: 'Exit fullscreen',
|
||||||
duration: null,
|
frameTitle: 'Player for {title}',
|
||||||
|
captions: 'Captions',
|
||||||
// Display the media duration on load in the current time position
|
settings: 'Settings',
|
||||||
// If you have opted to display both duration and currentTime, this is ignored
|
pip: 'PIP',
|
||||||
displayDuration: true,
|
menuBack: 'Go back to previous menu',
|
||||||
|
speed: 'Speed',
|
||||||
// Invert the current time to be a countdown
|
normal: 'Normal',
|
||||||
invertTime: true,
|
quality: 'Quality',
|
||||||
|
loop: 'Loop',
|
||||||
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
start: 'Start',
|
||||||
toggleInvert: true,
|
end: 'End',
|
||||||
|
all: 'All',
|
||||||
// Force an aspect ratio
|
reset: 'Reset',
|
||||||
// The format must be `'w:h'` (e.g. `'16:9'`)
|
disabled: 'Disabled',
|
||||||
ratio: null,
|
enabled: 'Enabled',
|
||||||
|
advertisement: 'Ad',
|
||||||
// Click video container to play/pause
|
qualityBadge: {
|
||||||
clickToPlay: true,
|
2160: '4K',
|
||||||
|
1440: 'HD',
|
||||||
// Auto hide the controls
|
1080: 'HD',
|
||||||
hideControls: true,
|
720: 'HD',
|
||||||
|
576: 'SD',
|
||||||
// Reset to start when playback ended
|
480: 'SD',
|
||||||
resetOnEnd: false,
|
|
||||||
|
|
||||||
// Disable the standard context menu
|
|
||||||
disableContextMenu: true,
|
|
||||||
|
|
||||||
// Sprite (for icons)
|
|
||||||
loadSprite: true,
|
|
||||||
iconPrefix: 'plyr',
|
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
|
||||||
|
|
||||||
// Quality default
|
|
||||||
quality: {
|
|
||||||
default: 576,
|
|
||||||
// The options to display in the UI, if available for the source media
|
|
||||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
|
||||||
forced: false,
|
|
||||||
onChange: null,
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Set loops
|
// URLs
|
||||||
loop: {
|
urls: {
|
||||||
active: false,
|
download: null,
|
||||||
// start: null,
|
|
||||||
// end: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Speed default and options to display
|
|
||||||
speed: {
|
|
||||||
selected: 1,
|
|
||||||
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
|
||||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Keyboard shortcut settings
|
|
||||||
keyboard: {
|
|
||||||
focused: true,
|
|
||||||
global: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Display tooltips
|
|
||||||
tooltips: {
|
|
||||||
controls: false,
|
|
||||||
seek: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Captions settings
|
|
||||||
captions: {
|
|
||||||
active: false,
|
|
||||||
language: 'auto',
|
|
||||||
// Listen to new tracks added after Plyr is initialized.
|
|
||||||
// This is needed for streaming captions, but may result in unselectable options
|
|
||||||
update: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fullscreen settings
|
|
||||||
fullscreen: {
|
|
||||||
enabled: true, // Allow fullscreen?
|
|
||||||
fallback: true, // Fallback using full viewport/window
|
|
||||||
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Local storage
|
|
||||||
storage: {
|
|
||||||
enabled: true,
|
|
||||||
key: 'plyr',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Default controls
|
|
||||||
controls: [
|
|
||||||
'play-large',
|
|
||||||
// 'restart',
|
|
||||||
// 'rewind',
|
|
||||||
'play',
|
|
||||||
// 'fast-forward',
|
|
||||||
'progress',
|
|
||||||
'current-time',
|
|
||||||
// 'duration',
|
|
||||||
'mute',
|
|
||||||
'volume',
|
|
||||||
'captions',
|
|
||||||
'settings',
|
|
||||||
'pip',
|
|
||||||
'airplay',
|
|
||||||
// 'download',
|
|
||||||
'fullscreen',
|
|
||||||
],
|
|
||||||
settings: ['captions', 'quality', 'speed'],
|
|
||||||
|
|
||||||
// Localisation
|
|
||||||
i18n: {
|
|
||||||
restart: 'Restart',
|
|
||||||
rewind: 'Rewind {seektime}s',
|
|
||||||
play: 'Play',
|
|
||||||
pause: 'Pause',
|
|
||||||
fastForward: 'Forward {seektime}s',
|
|
||||||
seek: 'Seek',
|
|
||||||
seekLabel: '{currentTime} of {duration}',
|
|
||||||
played: 'Played',
|
|
||||||
buffered: 'Buffered',
|
|
||||||
currentTime: 'Current time',
|
|
||||||
duration: 'Duration',
|
|
||||||
volume: 'Volume',
|
|
||||||
mute: 'Mute',
|
|
||||||
unmute: 'Unmute',
|
|
||||||
enableCaptions: 'Enable captions',
|
|
||||||
disableCaptions: 'Disable captions',
|
|
||||||
download: 'Download',
|
|
||||||
enterFullscreen: 'Enter fullscreen',
|
|
||||||
exitFullscreen: 'Exit fullscreen',
|
|
||||||
frameTitle: 'Player for {title}',
|
|
||||||
captions: 'Captions',
|
|
||||||
settings: 'Settings',
|
|
||||||
pip: 'PIP',
|
|
||||||
menuBack: 'Go back to previous menu',
|
|
||||||
speed: 'Speed',
|
|
||||||
normal: 'Normal',
|
|
||||||
quality: 'Quality',
|
|
||||||
loop: 'Loop',
|
|
||||||
start: 'Start',
|
|
||||||
end: 'End',
|
|
||||||
all: 'All',
|
|
||||||
reset: 'Reset',
|
|
||||||
disabled: 'Disabled',
|
|
||||||
enabled: 'Enabled',
|
|
||||||
advertisement: 'Ad',
|
|
||||||
qualityBadge: {
|
|
||||||
2160: '4K',
|
|
||||||
1440: 'HD',
|
|
||||||
1080: 'HD',
|
|
||||||
720: 'HD',
|
|
||||||
576: 'SD',
|
|
||||||
480: 'SD',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// URLs
|
|
||||||
urls: {
|
|
||||||
download: null,
|
|
||||||
vimeo: {
|
|
||||||
sdk: 'https://player.vimeo.com/api/player.js',
|
|
||||||
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
|
||||||
api: 'https://vimeo.com/api/v2/video/{0}.json',
|
|
||||||
},
|
|
||||||
youtube: {
|
|
||||||
sdk: 'https://www.youtube.com/iframe_api',
|
|
||||||
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
|
||||||
},
|
|
||||||
googleIMA: {
|
|
||||||
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Custom control listeners
|
|
||||||
listeners: {
|
|
||||||
seek: null,
|
|
||||||
play: null,
|
|
||||||
pause: null,
|
|
||||||
restart: null,
|
|
||||||
rewind: null,
|
|
||||||
fastForward: null,
|
|
||||||
mute: null,
|
|
||||||
volume: null,
|
|
||||||
captions: null,
|
|
||||||
download: null,
|
|
||||||
fullscreen: null,
|
|
||||||
pip: null,
|
|
||||||
airplay: null,
|
|
||||||
speed: null,
|
|
||||||
quality: null,
|
|
||||||
loop: null,
|
|
||||||
language: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Events to watch and bubble
|
|
||||||
events: [
|
|
||||||
// Events to watch on HTML5 media elements and bubble
|
|
||||||
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
|
|
||||||
'ended',
|
|
||||||
'progress',
|
|
||||||
'stalled',
|
|
||||||
'playing',
|
|
||||||
'waiting',
|
|
||||||
'canplay',
|
|
||||||
'canplaythrough',
|
|
||||||
'loadstart',
|
|
||||||
'loadeddata',
|
|
||||||
'loadedmetadata',
|
|
||||||
'timeupdate',
|
|
||||||
'volumechange',
|
|
||||||
'play',
|
|
||||||
'pause',
|
|
||||||
'error',
|
|
||||||
'seeking',
|
|
||||||
'seeked',
|
|
||||||
'emptied',
|
|
||||||
'ratechange',
|
|
||||||
'cuechange',
|
|
||||||
|
|
||||||
// Custom events
|
|
||||||
'download',
|
|
||||||
'enterfullscreen',
|
|
||||||
'exitfullscreen',
|
|
||||||
'captionsenabled',
|
|
||||||
'captionsdisabled',
|
|
||||||
'languagechange',
|
|
||||||
'controlshidden',
|
|
||||||
'controlsshown',
|
|
||||||
'ready',
|
|
||||||
|
|
||||||
// YouTube
|
|
||||||
'statechange',
|
|
||||||
|
|
||||||
// Quality
|
|
||||||
'qualitychange',
|
|
||||||
|
|
||||||
// Ads
|
|
||||||
'adsloaded',
|
|
||||||
'adscontentpause',
|
|
||||||
'adscontentresume',
|
|
||||||
'adstarted',
|
|
||||||
'adsmidpoint',
|
|
||||||
'adscomplete',
|
|
||||||
'adsallcomplete',
|
|
||||||
'adsimpression',
|
|
||||||
'adsclick',
|
|
||||||
],
|
|
||||||
|
|
||||||
// Selectors
|
|
||||||
// Change these to match your template if using custom HTML
|
|
||||||
selectors: {
|
|
||||||
editable: 'input, textarea, select, [contenteditable]',
|
|
||||||
container: '.plyr',
|
|
||||||
controls: {
|
|
||||||
container: null,
|
|
||||||
wrapper: '.plyr__controls',
|
|
||||||
},
|
|
||||||
labels: '[data-plyr]',
|
|
||||||
buttons: {
|
|
||||||
play: '[data-plyr="play"]',
|
|
||||||
pause: '[data-plyr="pause"]',
|
|
||||||
restart: '[data-plyr="restart"]',
|
|
||||||
rewind: '[data-plyr="rewind"]',
|
|
||||||
fastForward: '[data-plyr="fast-forward"]',
|
|
||||||
mute: '[data-plyr="mute"]',
|
|
||||||
captions: '[data-plyr="captions"]',
|
|
||||||
download: '[data-plyr="download"]',
|
|
||||||
fullscreen: '[data-plyr="fullscreen"]',
|
|
||||||
pip: '[data-plyr="pip"]',
|
|
||||||
airplay: '[data-plyr="airplay"]',
|
|
||||||
settings: '[data-plyr="settings"]',
|
|
||||||
loop: '[data-plyr="loop"]',
|
|
||||||
},
|
|
||||||
inputs: {
|
|
||||||
seek: '[data-plyr="seek"]',
|
|
||||||
volume: '[data-plyr="volume"]',
|
|
||||||
speed: '[data-plyr="speed"]',
|
|
||||||
language: '[data-plyr="language"]',
|
|
||||||
quality: '[data-plyr="quality"]',
|
|
||||||
},
|
|
||||||
display: {
|
|
||||||
currentTime: '.plyr__time--current',
|
|
||||||
duration: '.plyr__time--duration',
|
|
||||||
buffer: '.plyr__progress__buffer',
|
|
||||||
loop: '.plyr__progress__loop', // Used later
|
|
||||||
volume: '.plyr__volume--display',
|
|
||||||
},
|
|
||||||
progress: '.plyr__progress',
|
|
||||||
captions: '.plyr__captions',
|
|
||||||
caption: '.plyr__caption',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Class hooks added to the player in different states
|
|
||||||
classNames: {
|
|
||||||
type: 'plyr--{0}',
|
|
||||||
provider: 'plyr--{0}',
|
|
||||||
video: 'plyr__video-wrapper',
|
|
||||||
embed: 'plyr__video-embed',
|
|
||||||
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
|
|
||||||
embedContainer: 'plyr__video-embed__container',
|
|
||||||
poster: 'plyr__poster',
|
|
||||||
posterEnabled: 'plyr__poster-enabled',
|
|
||||||
ads: 'plyr__ads',
|
|
||||||
control: 'plyr__control',
|
|
||||||
controlPressed: 'plyr__control--pressed',
|
|
||||||
playing: 'plyr--playing',
|
|
||||||
paused: 'plyr--paused',
|
|
||||||
stopped: 'plyr--stopped',
|
|
||||||
loading: 'plyr--loading',
|
|
||||||
hover: 'plyr--hover',
|
|
||||||
tooltip: 'plyr__tooltip',
|
|
||||||
cues: 'plyr__cues',
|
|
||||||
hidden: 'plyr__sr-only',
|
|
||||||
hideControls: 'plyr--hide-controls',
|
|
||||||
isIos: 'plyr--is-ios',
|
|
||||||
isTouch: 'plyr--is-touch',
|
|
||||||
uiSupported: 'plyr--full-ui',
|
|
||||||
noTransition: 'plyr--no-transition',
|
|
||||||
display: {
|
|
||||||
time: 'plyr__time',
|
|
||||||
},
|
|
||||||
menu: {
|
|
||||||
value: 'plyr__menu__value',
|
|
||||||
badge: 'plyr__badge',
|
|
||||||
open: 'plyr--menu-open',
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
enabled: 'plyr--captions-enabled',
|
|
||||||
active: 'plyr--captions-active',
|
|
||||||
},
|
|
||||||
fullscreen: {
|
|
||||||
enabled: 'plyr--fullscreen-enabled',
|
|
||||||
fallback: 'plyr--fullscreen-fallback',
|
|
||||||
},
|
|
||||||
pip: {
|
|
||||||
supported: 'plyr--pip-supported',
|
|
||||||
active: 'plyr--pip-active',
|
|
||||||
},
|
|
||||||
airplay: {
|
|
||||||
supported: 'plyr--airplay-supported',
|
|
||||||
active: 'plyr--airplay-active',
|
|
||||||
},
|
|
||||||
tabFocus: 'plyr__tab-focus',
|
|
||||||
previewThumbnails: {
|
|
||||||
// Tooltip thumbs
|
|
||||||
thumbContainer: 'plyr__preview-thumb',
|
|
||||||
thumbContainerShown: 'plyr__preview-thumb--is-shown',
|
|
||||||
imageContainer: 'plyr__preview-thumb__image-container',
|
|
||||||
timeContainer: 'plyr__preview-thumb__time-container',
|
|
||||||
// Scrubbing
|
|
||||||
scrubbingContainer: 'plyr__preview-scrubbing',
|
|
||||||
scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Embed attributes
|
|
||||||
attributes: {
|
|
||||||
embed: {
|
|
||||||
provider: 'data-plyr-provider',
|
|
||||||
id: 'data-plyr-embed-id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Advertisements plugin
|
|
||||||
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
|
||||||
ads: {
|
|
||||||
enabled: false,
|
|
||||||
publisherId: '',
|
|
||||||
tagUrl: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Preview Thumbnails plugin
|
|
||||||
previewThumbnails: {
|
|
||||||
enabled: false,
|
|
||||||
src: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Vimeo plugin
|
|
||||||
vimeo: {
|
vimeo: {
|
||||||
byline: false,
|
sdk: 'https://player.vimeo.com/api/player.js',
|
||||||
portrait: false,
|
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
||||||
title: false,
|
api: 'https://vimeo.com/api/v2/video/{0}.json',
|
||||||
speed: true,
|
|
||||||
transparent: false,
|
|
||||||
// These settings require a pro or premium account to work
|
|
||||||
sidedock: false,
|
|
||||||
controls: false,
|
|
||||||
// Custom settings from Plyr
|
|
||||||
referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// YouTube plugin
|
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false, // Whether to use an alternative version of YouTube without cookies
|
sdk: 'https://www.youtube.com/iframe_api',
|
||||||
rel: 0, // No related vids
|
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
||||||
showinfo: 0, // Hide info
|
|
||||||
iv_load_policy: 3, // Hide annotations
|
|
||||||
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
|
|
||||||
},
|
},
|
||||||
|
googleIMA: {
|
||||||
|
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Custom control listeners
|
||||||
|
listeners: {
|
||||||
|
seek: null,
|
||||||
|
play: null,
|
||||||
|
pause: null,
|
||||||
|
restart: null,
|
||||||
|
rewind: null,
|
||||||
|
fastForward: null,
|
||||||
|
mute: null,
|
||||||
|
volume: null,
|
||||||
|
captions: null,
|
||||||
|
download: null,
|
||||||
|
fullscreen: null,
|
||||||
|
pip: null,
|
||||||
|
airplay: null,
|
||||||
|
speed: null,
|
||||||
|
quality: null,
|
||||||
|
loop: null,
|
||||||
|
language: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Events to watch and bubble
|
||||||
|
events: [
|
||||||
|
// Events to watch on HTML5 media elements and bubble
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
|
||||||
|
'ended',
|
||||||
|
'progress',
|
||||||
|
'stalled',
|
||||||
|
'playing',
|
||||||
|
'waiting',
|
||||||
|
'canplay',
|
||||||
|
'canplaythrough',
|
||||||
|
'loadstart',
|
||||||
|
'loadeddata',
|
||||||
|
'loadedmetadata',
|
||||||
|
'timeupdate',
|
||||||
|
'volumechange',
|
||||||
|
'play',
|
||||||
|
'pause',
|
||||||
|
'error',
|
||||||
|
'seeking',
|
||||||
|
'seeked',
|
||||||
|
'emptied',
|
||||||
|
'ratechange',
|
||||||
|
'cuechange',
|
||||||
|
|
||||||
|
// Custom events
|
||||||
|
'download',
|
||||||
|
'enterfullscreen',
|
||||||
|
'exitfullscreen',
|
||||||
|
'captionsenabled',
|
||||||
|
'captionsdisabled',
|
||||||
|
'languagechange',
|
||||||
|
'controlshidden',
|
||||||
|
'controlsshown',
|
||||||
|
'ready',
|
||||||
|
|
||||||
|
// YouTube
|
||||||
|
'statechange',
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
'qualitychange',
|
||||||
|
|
||||||
|
// Ads
|
||||||
|
'adsloaded',
|
||||||
|
'adscontentpause',
|
||||||
|
'adscontentresume',
|
||||||
|
'adstarted',
|
||||||
|
'adsmidpoint',
|
||||||
|
'adscomplete',
|
||||||
|
'adsallcomplete',
|
||||||
|
'adsimpression',
|
||||||
|
'adsclick',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
// Change these to match your template if using custom HTML
|
||||||
|
selectors: {
|
||||||
|
editable: 'input, textarea, select, [contenteditable]',
|
||||||
|
container: '.plyr',
|
||||||
|
controls: {
|
||||||
|
container: null,
|
||||||
|
wrapper: '.plyr__controls',
|
||||||
|
},
|
||||||
|
labels: '[data-plyr]',
|
||||||
|
buttons: {
|
||||||
|
play: '[data-plyr="play"]',
|
||||||
|
pause: '[data-plyr="pause"]',
|
||||||
|
restart: '[data-plyr="restart"]',
|
||||||
|
rewind: '[data-plyr="rewind"]',
|
||||||
|
fastForward: '[data-plyr="fast-forward"]',
|
||||||
|
mute: '[data-plyr="mute"]',
|
||||||
|
captions: '[data-plyr="captions"]',
|
||||||
|
download: '[data-plyr="download"]',
|
||||||
|
fullscreen: '[data-plyr="fullscreen"]',
|
||||||
|
pip: '[data-plyr="pip"]',
|
||||||
|
airplay: '[data-plyr="airplay"]',
|
||||||
|
settings: '[data-plyr="settings"]',
|
||||||
|
loop: '[data-plyr="loop"]',
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
seek: '[data-plyr="seek"]',
|
||||||
|
volume: '[data-plyr="volume"]',
|
||||||
|
speed: '[data-plyr="speed"]',
|
||||||
|
language: '[data-plyr="language"]',
|
||||||
|
quality: '[data-plyr="quality"]',
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
currentTime: '.plyr__time--current',
|
||||||
|
duration: '.plyr__time--duration',
|
||||||
|
buffer: '.plyr__progress__buffer',
|
||||||
|
loop: '.plyr__progress__loop', // Used later
|
||||||
|
volume: '.plyr__volume--display',
|
||||||
|
},
|
||||||
|
progress: '.plyr__progress',
|
||||||
|
captions: '.plyr__captions',
|
||||||
|
caption: '.plyr__caption',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Class hooks added to the player in different states
|
||||||
|
classNames: {
|
||||||
|
type: 'plyr--{0}',
|
||||||
|
provider: 'plyr--{0}',
|
||||||
|
video: 'plyr__video-wrapper',
|
||||||
|
embed: 'plyr__video-embed',
|
||||||
|
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
|
||||||
|
embedContainer: 'plyr__video-embed__container',
|
||||||
|
poster: 'plyr__poster',
|
||||||
|
posterEnabled: 'plyr__poster-enabled',
|
||||||
|
ads: 'plyr__ads',
|
||||||
|
control: 'plyr__control',
|
||||||
|
controlPressed: 'plyr__control--pressed',
|
||||||
|
playing: 'plyr--playing',
|
||||||
|
paused: 'plyr--paused',
|
||||||
|
stopped: 'plyr--stopped',
|
||||||
|
loading: 'plyr--loading',
|
||||||
|
hover: 'plyr--hover',
|
||||||
|
tooltip: 'plyr__tooltip',
|
||||||
|
cues: 'plyr__cues',
|
||||||
|
hidden: 'plyr__sr-only',
|
||||||
|
hideControls: 'plyr--hide-controls',
|
||||||
|
isIos: 'plyr--is-ios',
|
||||||
|
isTouch: 'plyr--is-touch',
|
||||||
|
uiSupported: 'plyr--full-ui',
|
||||||
|
noTransition: 'plyr--no-transition',
|
||||||
|
display: {
|
||||||
|
time: 'plyr__time',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
value: 'plyr__menu__value',
|
||||||
|
badge: 'plyr__badge',
|
||||||
|
open: 'plyr--menu-open',
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
enabled: 'plyr--captions-enabled',
|
||||||
|
active: 'plyr--captions-active',
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enabled: 'plyr--fullscreen-enabled',
|
||||||
|
fallback: 'plyr--fullscreen-fallback',
|
||||||
|
},
|
||||||
|
pip: {
|
||||||
|
supported: 'plyr--pip-supported',
|
||||||
|
active: 'plyr--pip-active',
|
||||||
|
},
|
||||||
|
airplay: {
|
||||||
|
supported: 'plyr--airplay-supported',
|
||||||
|
active: 'plyr--airplay-active',
|
||||||
|
},
|
||||||
|
tabFocus: 'plyr__tab-focus',
|
||||||
|
previewThumbnails: {
|
||||||
|
// Tooltip thumbs
|
||||||
|
thumbContainer: 'plyr__preview-thumb',
|
||||||
|
thumbContainerShown: 'plyr__preview-thumb--is-shown',
|
||||||
|
imageContainer: 'plyr__preview-thumb__image-container',
|
||||||
|
timeContainer: 'plyr__preview-thumb__time-container',
|
||||||
|
// Scrubbing
|
||||||
|
scrubbingContainer: 'plyr__preview-scrubbing',
|
||||||
|
scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Embed attributes
|
||||||
|
attributes: {
|
||||||
|
embed: {
|
||||||
|
provider: 'data-plyr-provider',
|
||||||
|
id: 'data-plyr-embed-id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Advertisements plugin
|
||||||
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
|
ads: {
|
||||||
|
enabled: false,
|
||||||
|
publisherId: '',
|
||||||
|
tagUrl: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Preview Thumbnails plugin
|
||||||
|
previewThumbnails: {
|
||||||
|
enabled: false,
|
||||||
|
src: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vimeo plugin
|
||||||
|
vimeo: {
|
||||||
|
byline: false,
|
||||||
|
portrait: false,
|
||||||
|
title: false,
|
||||||
|
speed: true,
|
||||||
|
transparent: false,
|
||||||
|
// These settings require a pro or premium account to work
|
||||||
|
sidedock: false,
|
||||||
|
controls: false,
|
||||||
|
// Custom settings from Plyr
|
||||||
|
referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
},
|
||||||
|
|
||||||
|
// YouTube plugin
|
||||||
|
youtube: {
|
||||||
|
noCookie: false, // Whether to use an alternative version of YouTube without cookies
|
||||||
|
rel: 0, // No related vids
|
||||||
|
showinfo: 0, // Hide info
|
||||||
|
iv_load_policy: 3, // Hide annotations
|
||||||
|
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaults;
|
export default defaults;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export const pip = {
|
export const pip = {
|
||||||
active: 'picture-in-picture',
|
active: 'picture-in-picture',
|
||||||
inactive: 'inline',
|
inactive: 'inline',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { pip };
|
export default { pip };
|
||||||
|
|||||||
+14
-14
@@ -3,14 +3,14 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export const providers = {
|
export const providers = {
|
||||||
html5: 'html5',
|
html5: 'html5',
|
||||||
youtube: 'youtube',
|
youtube: 'youtube',
|
||||||
vimeo: 'vimeo',
|
vimeo: 'vimeo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const types = {
|
export const types = {
|
||||||
audio: 'audio',
|
audio: 'audio',
|
||||||
video: 'video',
|
video: 'video',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,17 +18,17 @@ export const types = {
|
|||||||
* @param {String} url
|
* @param {String} url
|
||||||
*/
|
*/
|
||||||
export function getProviderByUrl(url) {
|
export function getProviderByUrl(url) {
|
||||||
// YouTube
|
// YouTube
|
||||||
if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(url)) {
|
if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(url)) {
|
||||||
return providers.youtube;
|
return providers.youtube;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vimeo
|
// Vimeo
|
||||||
if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
|
if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
|
||||||
return providers.vimeo;
|
return providers.vimeo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { providers, types };
|
export default { providers, types };
|
||||||
|
|||||||
+17
-17
@@ -5,26 +5,26 @@
|
|||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
export default class Console {
|
export default class Console {
|
||||||
constructor(enabled = false) {
|
constructor(enabled = false) {
|
||||||
this.enabled = window.console && enabled;
|
this.enabled = window.console && enabled;
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
this.log('Debugging enabled');
|
this.log('Debugging enabled');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get log() {
|
get log() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
get warn() {
|
get warn() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
get error() {
|
get error() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1582
-1584
File diff suppressed because it is too large
Load Diff
+255
-258
@@ -11,283 +11,280 @@ import is from './utils/is';
|
|||||||
import { silencePromise } from './utils/promise';
|
import { silencePromise } from './utils/promise';
|
||||||
|
|
||||||
class Fullscreen {
|
class Fullscreen {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
// Keep reference to parent
|
// Keep reference to parent
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
|
||||||
// Get prefix
|
// Get prefix
|
||||||
this.prefix = Fullscreen.prefix;
|
this.prefix = Fullscreen.prefix;
|
||||||
this.property = Fullscreen.property;
|
this.property = Fullscreen.property;
|
||||||
|
|
||||||
// Scroll position
|
// Scroll position
|
||||||
this.scrollPosition = { x: 0, y: 0 };
|
this.scrollPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
// Force the use of 'full window/browser' rather than fullscreen
|
// Force the use of 'full window/browser' rather than fullscreen
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force';
|
this.forceFallback = player.config.fullscreen.fallback === 'force';
|
||||||
|
|
||||||
// Register event listeners
|
// Register event listeners
|
||||||
// Handle event (incase user presses escape etc)
|
// Handle event (incase user presses escape etc)
|
||||||
on.call(
|
on.call(
|
||||||
this.player,
|
this.player,
|
||||||
document,
|
document,
|
||||||
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
||||||
() => {
|
() => {
|
||||||
// TODO: Filter for target??
|
// TODO: Filter for target??
|
||||||
this.onChange();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fullscreen toggle on double click
|
|
||||||
on.call(this.player, this.player.elements.container, 'dblclick', event => {
|
|
||||||
// Ignore double click in controls
|
|
||||||
if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tap focus when in fullscreen
|
|
||||||
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
|
||||||
|
|
||||||
// Update the UI
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if native supported
|
|
||||||
static get native() {
|
|
||||||
return !!(
|
|
||||||
document.fullscreenEnabled ||
|
|
||||||
document.webkitFullscreenEnabled ||
|
|
||||||
document.mozFullScreenEnabled ||
|
|
||||||
document.msFullscreenEnabled
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're actually using native
|
|
||||||
get usingNative() {
|
|
||||||
return Fullscreen.native && !this.forceFallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the prefix for handlers
|
|
||||||
static get prefix() {
|
|
||||||
// No prefix
|
|
||||||
if (is.function(document.exitFullscreen)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for fullscreen support by vendor prefix
|
|
||||||
let value = '';
|
|
||||||
const prefixes = ['webkit', 'moz', 'ms'];
|
|
||||||
|
|
||||||
prefixes.some(pre => {
|
|
||||||
if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {
|
|
||||||
value = pre;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get property() {
|
|
||||||
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if fullscreen is enabled
|
|
||||||
get enabled() {
|
|
||||||
return (
|
|
||||||
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
|
||||||
this.player.config.fullscreen.enabled &&
|
|
||||||
this.player.supported.ui &&
|
|
||||||
this.player.isVideo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get active state
|
|
||||||
get active() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback using classname
|
|
||||||
if (!Fullscreen.native || this.forceFallback) {
|
|
||||||
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
|
||||||
|
|
||||||
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get target element
|
|
||||||
get target() {
|
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative
|
|
||||||
? this.player.media
|
|
||||||
: this.player.elements.container;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update toggle button
|
|
||||||
const button = this.player.elements.buttons.fullscreen;
|
|
||||||
if (is.element(button)) {
|
|
||||||
button.pressed = this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger an event
|
|
||||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFallback(toggle = false) {
|
|
||||||
// Store or restore scroll position
|
|
||||||
if (toggle) {
|
|
||||||
this.scrollPosition = {
|
|
||||||
x: window.scrollX || 0,
|
|
||||||
y: window.scrollY || 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle scroll
|
|
||||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
|
||||||
|
|
||||||
// Toggle class hook
|
|
||||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
|
||||||
|
|
||||||
// Force full viewport on iPhone X+
|
|
||||||
if (browser.isIos) {
|
|
||||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
|
||||||
const property = 'viewport-fit=cover';
|
|
||||||
|
|
||||||
// Inject the viewport meta if required
|
|
||||||
if (!viewport) {
|
|
||||||
viewport = document.createElement('meta');
|
|
||||||
viewport.setAttribute('name', 'viewport');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the property already exists
|
|
||||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
|
||||||
|
|
||||||
if (toggle) {
|
|
||||||
this.cleanupViewport = !hasProperty;
|
|
||||||
|
|
||||||
if (!hasProperty) {
|
|
||||||
viewport.content += `,${property}`;
|
|
||||||
}
|
|
||||||
} else if (this.cleanupViewport) {
|
|
||||||
viewport.content = viewport.content
|
|
||||||
.split(',')
|
|
||||||
.filter(part => part.trim() !== property)
|
|
||||||
.join(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle button and fire events
|
|
||||||
this.onChange();
|
this.onChange();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fullscreen toggle on double click
|
||||||
|
on.call(this.player, this.player.elements.container, 'dblclick', event => {
|
||||||
|
// Ignore double click in controls
|
||||||
|
if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tap focus when in fullscreen
|
||||||
|
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if native supported
|
||||||
|
static get native() {
|
||||||
|
return !!(
|
||||||
|
document.fullscreenEnabled ||
|
||||||
|
document.webkitFullscreenEnabled ||
|
||||||
|
document.mozFullScreenEnabled ||
|
||||||
|
document.msFullscreenEnabled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're actually using native
|
||||||
|
get usingNative() {
|
||||||
|
return Fullscreen.native && !this.forceFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the prefix for handlers
|
||||||
|
static get prefix() {
|
||||||
|
// No prefix
|
||||||
|
if (is.function(document.exitFullscreen)) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trap focus inside container
|
// Check for fullscreen support by vendor prefix
|
||||||
trapFocus(event) {
|
let value = '';
|
||||||
// Bail if iOS, not active, not the tab key
|
const prefixes = ['webkit', 'moz', 'ms'];
|
||||||
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current focused element
|
prefixes.some(pre => {
|
||||||
const focused = document.activeElement;
|
if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {
|
||||||
const focusable = getElements.call(
|
value = pre;
|
||||||
this.player,
|
return true;
|
||||||
'a[href], button:not(:disabled), input:not(:disabled), [tabindex]',
|
}
|
||||||
);
|
|
||||||
const [first] = focusable;
|
|
||||||
const last = focusable[focusable.length - 1];
|
|
||||||
|
|
||||||
if (focused === last && !event.shiftKey) {
|
return false;
|
||||||
// Move focus to first element that can be tabbed if Shift isn't used
|
});
|
||||||
first.focus();
|
|
||||||
event.preventDefault();
|
return value;
|
||||||
} else if (focused === first && event.shiftKey) {
|
}
|
||||||
// Move focus to last element that can be tabbed if Shift is used
|
|
||||||
last.focus();
|
static get property() {
|
||||||
event.preventDefault();
|
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if fullscreen is enabled
|
||||||
|
get enabled() {
|
||||||
|
return (
|
||||||
|
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
||||||
|
this.player.config.fullscreen.enabled &&
|
||||||
|
this.player.supported.ui &&
|
||||||
|
this.player.isVideo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active state
|
||||||
|
get active() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Fallback using classname
|
||||||
update() {
|
if (!Fullscreen.native || this.forceFallback) {
|
||||||
if (this.enabled) {
|
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||||
let mode;
|
|
||||||
|
|
||||||
if (this.forceFallback) {
|
|
||||||
mode = 'Fallback (forced)';
|
|
||||||
} else if (Fullscreen.native) {
|
|
||||||
mode = 'Native';
|
|
||||||
} else {
|
|
||||||
mode = 'Fallback';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.debug.log(`${mode} fullscreen enabled`);
|
|
||||||
} else {
|
|
||||||
this.player.debug.log('Fullscreen not supported and fallback disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add styling hook to show button
|
|
||||||
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make an element fullscreen
|
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||||
enter() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// iOS native fullscreen doesn't need the request step
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
}
|
||||||
this.target.webkitEnterFullscreen();
|
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
// Get target element
|
||||||
this.toggleFallback(true);
|
get target() {
|
||||||
} else if (!this.prefix) {
|
return browser.isIos && this.player.config.fullscreen.iosNative
|
||||||
this.target.requestFullscreen({ navigationUI: 'hide' });
|
? this.player.media
|
||||||
} else if (!is.empty(this.prefix)) {
|
: this.player.elements.container;
|
||||||
this.target[`${this.prefix}Request${this.property}`]();
|
}
|
||||||
}
|
|
||||||
|
onChange() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail from fullscreen
|
// Update toggle button
|
||||||
exit() {
|
const button = this.player.elements.buttons.fullscreen;
|
||||||
if (!this.enabled) {
|
if (is.element(button)) {
|
||||||
return;
|
button.pressed = this.active;
|
||||||
}
|
|
||||||
|
|
||||||
// iOS native fullscreen
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
|
||||||
this.target.webkitExitFullscreen();
|
|
||||||
silencePromise(this.player.play());
|
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
|
||||||
this.toggleFallback(false);
|
|
||||||
} else if (!this.prefix) {
|
|
||||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
|
||||||
} else if (!is.empty(this.prefix)) {
|
|
||||||
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
|
||||||
document[`${this.prefix}${action}${this.property}`]();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle state
|
// Trigger an event
|
||||||
toggle() {
|
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||||
if (!this.active) {
|
}
|
||||||
this.enter();
|
|
||||||
} else {
|
toggleFallback(toggle = false) {
|
||||||
this.exit();
|
// Store or restore scroll position
|
||||||
}
|
if (toggle) {
|
||||||
|
this.scrollPosition = {
|
||||||
|
x: window.scrollX || 0,
|
||||||
|
y: window.scrollY || 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle scroll
|
||||||
|
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||||
|
|
||||||
|
// Toggle class hook
|
||||||
|
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||||
|
|
||||||
|
// Force full viewport on iPhone X+
|
||||||
|
if (browser.isIos) {
|
||||||
|
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||||
|
const property = 'viewport-fit=cover';
|
||||||
|
|
||||||
|
// Inject the viewport meta if required
|
||||||
|
if (!viewport) {
|
||||||
|
viewport = document.createElement('meta');
|
||||||
|
viewport.setAttribute('name', 'viewport');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the property already exists
|
||||||
|
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
this.cleanupViewport = !hasProperty;
|
||||||
|
|
||||||
|
if (!hasProperty) {
|
||||||
|
viewport.content += `,${property}`;
|
||||||
|
}
|
||||||
|
} else if (this.cleanupViewport) {
|
||||||
|
viewport.content = viewport.content
|
||||||
|
.split(',')
|
||||||
|
.filter(part => part.trim() !== property)
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle button and fire events
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trap focus inside container
|
||||||
|
trapFocus(event) {
|
||||||
|
// Bail if iOS, not active, not the tab key
|
||||||
|
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current focused element
|
||||||
|
const focused = document.activeElement;
|
||||||
|
const focusable = getElements.call(this.player, 'a[href], button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||||
|
const [first] = focusable;
|
||||||
|
const last = focusable[focusable.length - 1];
|
||||||
|
|
||||||
|
if (focused === last && !event.shiftKey) {
|
||||||
|
// Move focus to first element that can be tabbed if Shift isn't used
|
||||||
|
first.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (focused === first && event.shiftKey) {
|
||||||
|
// Move focus to last element that can be tabbed if Shift is used
|
||||||
|
last.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
update() {
|
||||||
|
if (this.enabled) {
|
||||||
|
let mode;
|
||||||
|
|
||||||
|
if (this.forceFallback) {
|
||||||
|
mode = 'Fallback (forced)';
|
||||||
|
} else if (Fullscreen.native) {
|
||||||
|
mode = 'Native';
|
||||||
|
} else {
|
||||||
|
mode = 'Fallback';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.debug.log(`${mode} fullscreen enabled`);
|
||||||
|
} else {
|
||||||
|
this.player.debug.log('Fullscreen not supported and fallback disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add styling hook to show button
|
||||||
|
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an element fullscreen
|
||||||
|
enter() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS native fullscreen doesn't need the request step
|
||||||
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
this.target.webkitEnterFullscreen();
|
||||||
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
|
this.toggleFallback(true);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
this.target.requestFullscreen({ navigationUI: 'hide' });
|
||||||
|
} else if (!is.empty(this.prefix)) {
|
||||||
|
this.target[`${this.prefix}Request${this.property}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bail from fullscreen
|
||||||
|
exit() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS native fullscreen
|
||||||
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
this.target.webkitExitFullscreen();
|
||||||
|
silencePromise(this.player.play());
|
||||||
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
|
this.toggleFallback(false);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||||
|
} else if (!is.empty(this.prefix)) {
|
||||||
|
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
||||||
|
document[`${this.prefix}${action}${this.property}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle state
|
||||||
|
toggle() {
|
||||||
|
if (!this.active) {
|
||||||
|
this.enter();
|
||||||
|
} else {
|
||||||
|
this.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Fullscreen;
|
export default Fullscreen;
|
||||||
|
|||||||
+122
-122
@@ -10,138 +10,138 @@ import { silencePromise } from './utils/promise';
|
|||||||
import { setAspectRatio } from './utils/style';
|
import { setAspectRatio } from './utils/style';
|
||||||
|
|
||||||
const html5 = {
|
const html5 = {
|
||||||
getSources() {
|
getSources() {
|
||||||
if (!this.isHTML5) {
|
if (!this.isHTML5) {
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sources = Array.from(this.media.querySelectorAll('source'));
|
||||||
|
|
||||||
|
// Filter out unsupported sources (if type is specified)
|
||||||
|
return sources.filter(source => {
|
||||||
|
const type = source.getAttribute('type');
|
||||||
|
|
||||||
|
if (is.empty(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return support.mime.call(this, type);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get quality levels
|
||||||
|
getQualityOptions() {
|
||||||
|
// Whether we're forcing all options (e.g. for streaming)
|
||||||
|
if (this.config.quality.forced) {
|
||||||
|
return this.config.quality.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sizes from <source> elements
|
||||||
|
return html5.getSources
|
||||||
|
.call(this)
|
||||||
|
.map(source => Number(source.getAttribute('size')))
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = this;
|
||||||
|
|
||||||
|
// Set speed options from config
|
||||||
|
player.options.speed = player.config.speed.options;
|
||||||
|
|
||||||
|
// Set aspect ratio if fixed
|
||||||
|
if (!is.empty(this.config.ratio)) {
|
||||||
|
setAspectRatio.call(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
Object.defineProperty(player.media, 'quality', {
|
||||||
|
get() {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
const source = sources.find(s => s.getAttribute('src') === player.source);
|
||||||
|
|
||||||
|
// Return size, if match is found
|
||||||
|
return source && Number(source.getAttribute('size'));
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
if (player.quality === input) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sources = Array.from(this.media.querySelectorAll('source'));
|
// If we're using an an external handler...
|
||||||
|
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
||||||
|
player.config.quality.onChange(input);
|
||||||
|
} else {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
// Get first match for requested size
|
||||||
|
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||||
|
|
||||||
// Filter out unsupported sources (if type is specified)
|
// No matching source found
|
||||||
return sources.filter(source => {
|
if (!source) {
|
||||||
const type = source.getAttribute('type');
|
|
||||||
|
|
||||||
if (is.empty(type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return support.mime.call(this, type);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get quality levels
|
|
||||||
getQualityOptions() {
|
|
||||||
// Whether we're forcing all options (e.g. for streaming)
|
|
||||||
if (this.config.quality.forced) {
|
|
||||||
return this.config.quality.options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get sizes from <source> elements
|
|
||||||
return html5.getSources
|
|
||||||
.call(this)
|
|
||||||
.map(source => Number(source.getAttribute('size')))
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
if (!this.isHTML5) {
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
||||||
|
|
||||||
|
// Set new source
|
||||||
|
player.media.src = source.getAttribute('src');
|
||||||
|
|
||||||
|
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||||
|
if (preload !== 'none' || readyState) {
|
||||||
|
// Restore time
|
||||||
|
player.once('loadedmetadata', () => {
|
||||||
|
player.speed = playbackRate;
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
|
||||||
|
// Resume playing
|
||||||
|
if (!paused) {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load new source
|
||||||
|
player.media.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = this;
|
// Trigger change event
|
||||||
|
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
// Set speed options from config
|
quality: input,
|
||||||
player.options.speed = player.config.speed.options;
|
|
||||||
|
|
||||||
// Set aspect ratio if fixed
|
|
||||||
if (!is.empty(this.config.ratio)) {
|
|
||||||
setAspectRatio.call(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quality
|
|
||||||
Object.defineProperty(player.media, 'quality', {
|
|
||||||
get() {
|
|
||||||
// Get sources
|
|
||||||
const sources = html5.getSources.call(player);
|
|
||||||
const source = sources.find(s => s.getAttribute('src') === player.source);
|
|
||||||
|
|
||||||
// Return size, if match is found
|
|
||||||
return source && Number(source.getAttribute('size'));
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
if (player.quality === input) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're using an an external handler...
|
|
||||||
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
|
||||||
player.config.quality.onChange(input);
|
|
||||||
} else {
|
|
||||||
// Get sources
|
|
||||||
const sources = html5.getSources.call(player);
|
|
||||||
// Get first match for requested size
|
|
||||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
|
||||||
|
|
||||||
// No matching source found
|
|
||||||
if (!source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current state
|
|
||||||
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
|
||||||
|
|
||||||
// Set new source
|
|
||||||
player.media.src = source.getAttribute('src');
|
|
||||||
|
|
||||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
|
||||||
if (preload !== 'none' || readyState) {
|
|
||||||
// Restore time
|
|
||||||
player.once('loadedmetadata', () => {
|
|
||||||
player.speed = playbackRate;
|
|
||||||
player.currentTime = currentTime;
|
|
||||||
|
|
||||||
// Resume playing
|
|
||||||
if (!paused) {
|
|
||||||
silencePromise(player.play());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load new source
|
|
||||||
player.media.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger change event
|
|
||||||
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
|
||||||
quality: input,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Cancel current network requests
|
// Cancel current network requests
|
||||||
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
|
cancelRequests() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove child sources
|
||||||
|
removeElement(html5.getSources.call(this));
|
||||||
|
|
||||||
|
// Set blank video src attribute
|
||||||
|
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
||||||
|
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
||||||
|
this.media.setAttribute('src', this.config.blankVideo);
|
||||||
|
|
||||||
|
// Load the new empty source
|
||||||
|
// This will cancel existing requests
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
cancelRequests() {
|
this.media.load();
|
||||||
if (!this.isHTML5) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove child sources
|
// Debugging
|
||||||
removeElement(html5.getSources.call(this));
|
this.debug.log('Cancelled network requests');
|
||||||
|
},
|
||||||
// Set blank video src attribute
|
|
||||||
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
|
||||||
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
|
||||||
this.media.setAttribute('src', this.config.blankVideo);
|
|
||||||
|
|
||||||
// Load the new empty source
|
|
||||||
// This will cancel existing requests
|
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
|
||||||
this.media.load();
|
|
||||||
|
|
||||||
// Debugging
|
|
||||||
this.debug.log('Cancelled network requests');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default html5;
|
export default html5;
|
||||||
|
|||||||
+805
-805
File diff suppressed because it is too large
Load Diff
+40
-40
@@ -8,54 +8,54 @@ import youtube from './plugins/youtube';
|
|||||||
import { createElement, toggleClass, wrap } from './utils/elements';
|
import { createElement, toggleClass, wrap } from './utils/elements';
|
||||||
|
|
||||||
const media = {
|
const media = {
|
||||||
// Setup media
|
// Setup media
|
||||||
setup() {
|
setup() {
|
||||||
// If there's no media, bail
|
// If there's no media, bail
|
||||||
if (!this.media) {
|
if (!this.media) {
|
||||||
this.debug.warn('No media element found!');
|
this.debug.warn('No media element found!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add type class
|
// Add type class
|
||||||
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
|
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
|
||||||
|
|
||||||
// Add provider class
|
// Add provider class
|
||||||
toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
|
toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
|
||||||
|
|
||||||
// Add video class for embeds
|
// Add video class for embeds
|
||||||
// This will require changes if audio embeds are added
|
// This will require changes if audio embeds are added
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
|
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the player wrapper
|
// Inject the player wrapper
|
||||||
if (this.isVideo) {
|
if (this.isVideo) {
|
||||||
// Create the wrapper div
|
// Create the wrapper div
|
||||||
this.elements.wrapper = createElement('div', {
|
this.elements.wrapper = createElement('div', {
|
||||||
class: this.config.classNames.video,
|
class: this.config.classNames.video,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wrap the video in a container
|
// Wrap the video in a container
|
||||||
wrap(this.media, this.elements.wrapper);
|
wrap(this.media, this.elements.wrapper);
|
||||||
|
|
||||||
// Faux poster container
|
// Faux poster container
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
this.elements.poster = createElement('div', {
|
this.elements.poster = createElement('div', {
|
||||||
class: this.config.classNames.poster,
|
class: this.config.classNames.poster,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
html5.setup.call(this);
|
html5.setup.call(this);
|
||||||
} else if (this.isYouTube) {
|
} else if (this.isYouTube) {
|
||||||
youtube.setup.call(this);
|
youtube.setup.call(this);
|
||||||
} else if (this.isVimeo) {
|
} else if (this.isVimeo) {
|
||||||
vimeo.setup.call(this);
|
vimeo.setup.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default media;
|
export default media;
|
||||||
|
|||||||
+570
-570
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+327
-327
@@ -17,392 +17,392 @@ import { buildUrlParams } from '../utils/urls';
|
|||||||
|
|
||||||
// Parse Vimeo ID from URL
|
// Parse Vimeo ID from URL
|
||||||
function parseId(url) {
|
function parseId(url) {
|
||||||
if (is.empty(url)) {
|
if (is.empty(url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is.number(Number(url))) {
|
if (is.number(Number(url))) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
|
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
|
||||||
return url.match(regex) ? RegExp.$2 : url;
|
return url.match(regex) ? RegExp.$2 : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set playback state and trigger change (only on actual change)
|
// Set playback state and trigger change (only on actual change)
|
||||||
function assurePlaybackState(play) {
|
function assurePlaybackState(play) {
|
||||||
if (play && !this.embed.hasPlayed) {
|
if (play && !this.embed.hasPlayed) {
|
||||||
this.embed.hasPlayed = true;
|
this.embed.hasPlayed = true;
|
||||||
}
|
}
|
||||||
if (this.media.paused === play) {
|
if (this.media.paused === play) {
|
||||||
this.media.paused = !play;
|
this.media.paused = !play;
|
||||||
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const vimeo = {
|
const vimeo = {
|
||||||
setup() {
|
setup() {
|
||||||
const player = this;
|
const player = this;
|
||||||
|
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
|
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
|
||||||
|
|
||||||
// Set speed options from config
|
// Set speed options from config
|
||||||
player.options.speed = player.config.speed.options;
|
player.options.speed = player.config.speed.options;
|
||||||
|
|
||||||
// Set intial ratio
|
// Set intial ratio
|
||||||
setAspectRatio.call(player);
|
setAspectRatio.call(player);
|
||||||
|
|
||||||
// Load the SDK if not already
|
// Load the SDK if not already
|
||||||
if (!is.object(window.Vimeo)) {
|
if (!is.object(window.Vimeo)) {
|
||||||
loadScript(player.config.urls.vimeo.sdk)
|
loadScript(player.config.urls.vimeo.sdk)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
vimeo.ready.call(player);
|
vimeo.ready.call(player);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
vimeo.ready.call(player);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// API Ready
|
|
||||||
ready() {
|
|
||||||
const player = this;
|
|
||||||
const config = player.config.vimeo;
|
|
||||||
|
|
||||||
// Get Vimeo params for the iframe
|
|
||||||
const params = buildUrlParams(
|
|
||||||
extend(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
loop: player.config.loop.active,
|
|
||||||
autoplay: player.autoplay,
|
|
||||||
muted: player.muted,
|
|
||||||
gesture: 'media',
|
|
||||||
playsinline: !this.config.fullscreen.iosNative,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the source URL or ID
|
|
||||||
let source = player.media.getAttribute('src');
|
|
||||||
|
|
||||||
// Get from <div> if needed
|
|
||||||
if (is.empty(source)) {
|
|
||||||
source = player.media.getAttribute(player.config.attributes.embed.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = parseId(source);
|
|
||||||
// Build an iframe
|
|
||||||
const iframe = createElement('iframe');
|
|
||||||
const src = format(player.config.urls.vimeo.iframe, id, params);
|
|
||||||
iframe.setAttribute('src', src);
|
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
|
||||||
iframe.setAttribute('allowtransparency', '');
|
|
||||||
iframe.setAttribute('allow', 'autoplay');
|
|
||||||
|
|
||||||
// Set the referrer policy if required
|
|
||||||
if (!is.empty(config.referrerPolicy)) {
|
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get poster, if already set
|
|
||||||
const { poster } = player;
|
|
||||||
// Inject the package
|
|
||||||
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media);
|
|
||||||
|
|
||||||
// Get poster image
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
|
|
||||||
if (is.empty(response)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the URL for thumbnail
|
|
||||||
const url = new URL(response[0].thumbnail_large);
|
|
||||||
|
|
||||||
// Get original image
|
|
||||||
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
|
|
||||||
|
|
||||||
// Set and show poster
|
|
||||||
ui.setPoster.call(player, url.href).catch(() => {});
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
vimeo.ready.call(player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Setup instance
|
// API Ready
|
||||||
// https://github.com/vimeo/player.js
|
ready() {
|
||||||
player.embed = new window.Vimeo.Player(iframe, {
|
const player = this;
|
||||||
autopause: player.config.autopause,
|
const config = player.config.vimeo;
|
||||||
muted: player.muted,
|
|
||||||
});
|
|
||||||
|
|
||||||
player.media.paused = true;
|
// Get Vimeo params for the iframe
|
||||||
player.media.currentTime = 0;
|
const params = buildUrlParams(
|
||||||
|
extend(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
loop: player.config.loop.active,
|
||||||
|
autoplay: player.autoplay,
|
||||||
|
muted: player.muted,
|
||||||
|
gesture: 'media',
|
||||||
|
playsinline: !this.config.fullscreen.iosNative,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Disable native text track rendering
|
// Get the source URL or ID
|
||||||
if (player.supported.ui) {
|
let source = player.media.getAttribute('src');
|
||||||
player.embed.disableTextTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a faux HTML5 API using the Vimeo API
|
// Get from <div> if needed
|
||||||
player.media.play = () => {
|
if (is.empty(source)) {
|
||||||
assurePlaybackState.call(player, true);
|
source = player.media.getAttribute(player.config.attributes.embed.id);
|
||||||
return player.embed.play();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
player.media.pause = () => {
|
const id = parseId(source);
|
||||||
assurePlaybackState.call(player, false);
|
// Build an iframe
|
||||||
return player.embed.pause();
|
const iframe = createElement('iframe');
|
||||||
};
|
const src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
|
iframe.setAttribute('src', src);
|
||||||
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
|
iframe.setAttribute('allowtransparency', '');
|
||||||
|
iframe.setAttribute('allow', 'autoplay');
|
||||||
|
|
||||||
player.media.stop = () => {
|
// Set the referrer policy if required
|
||||||
player.pause();
|
if (!is.empty(config.referrerPolicy)) {
|
||||||
player.currentTime = 0;
|
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Seeking
|
// Get poster, if already set
|
||||||
let { currentTime } = player.media;
|
const { poster } = player;
|
||||||
Object.defineProperty(player.media, 'currentTime', {
|
// Inject the package
|
||||||
get() {
|
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
|
||||||
return currentTime;
|
wrapper.appendChild(iframe);
|
||||||
},
|
player.media = replaceElement(wrapper, player.media);
|
||||||
set(time) {
|
|
||||||
// Vimeo will automatically play on seek if the video hasn't been played before
|
|
||||||
|
|
||||||
// Get current paused state and volume etc
|
// Get poster image
|
||||||
const { embed, media, paused, volume } = player;
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
|
||||||
const restorePause = paused && !embed.hasPlayed;
|
if (is.empty(response)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set seeking state and trigger event
|
// Get the URL for thumbnail
|
||||||
media.seeking = true;
|
const url = new URL(response[0].thumbnail_large);
|
||||||
triggerEvent.call(player, media, 'seeking');
|
|
||||||
|
|
||||||
// If paused, mute until seek is complete
|
// Get original image
|
||||||
Promise.resolve(restorePause && embed.setVolume(0))
|
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
|
||||||
// Seek
|
|
||||||
.then(() => embed.setCurrentTime(time))
|
|
||||||
// Restore paused
|
|
||||||
.then(() => restorePause && embed.pause())
|
|
||||||
// Restore volume
|
|
||||||
.then(() => restorePause && embed.setVolume(volume))
|
|
||||||
.catch(() => {
|
|
||||||
// Do nothing
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Playback speed
|
// Set and show poster
|
||||||
let speed = player.config.speed.selected;
|
ui.setPoster.call(player, url.href).catch(() => {});
|
||||||
Object.defineProperty(player.media, 'playbackRate', {
|
});
|
||||||
get() {
|
|
||||||
return speed;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
player.embed
|
|
||||||
.setPlaybackRate(input)
|
|
||||||
.then(() => {
|
|
||||||
speed = input;
|
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Cannot set Playback Rate, Video is probably not on Pro account
|
|
||||||
player.options.speed = [1];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume
|
// Setup instance
|
||||||
let { volume } = player.config;
|
// https://github.com/vimeo/player.js
|
||||||
Object.defineProperty(player.media, 'volume', {
|
player.embed = new window.Vimeo.Player(iframe, {
|
||||||
get() {
|
autopause: player.config.autopause,
|
||||||
return volume;
|
muted: player.muted,
|
||||||
},
|
});
|
||||||
set(input) {
|
|
||||||
player.embed.setVolume(input).then(() => {
|
|
||||||
volume = input;
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Muted
|
player.media.paused = true;
|
||||||
let { muted } = player.config;
|
player.media.currentTime = 0;
|
||||||
Object.defineProperty(player.media, 'muted', {
|
|
||||||
get() {
|
|
||||||
return muted;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : false;
|
|
||||||
|
|
||||||
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
// Disable native text track rendering
|
||||||
muted = toggle;
|
if (player.supported.ui) {
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
player.embed.disableTextTrack();
|
||||||
});
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Loop
|
// Create a faux HTML5 API using the Vimeo API
|
||||||
let { loop } = player.config;
|
player.media.play = () => {
|
||||||
Object.defineProperty(player.media, 'loop', {
|
assurePlaybackState.call(player, true);
|
||||||
get() {
|
return player.embed.play();
|
||||||
return loop;
|
};
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : player.config.loop.active;
|
|
||||||
|
|
||||||
player.embed.setLoop(toggle).then(() => {
|
player.media.pause = () => {
|
||||||
loop = toggle;
|
assurePlaybackState.call(player, false);
|
||||||
});
|
return player.embed.pause();
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
// Source
|
player.media.stop = () => {
|
||||||
let currentSrc;
|
player.pause();
|
||||||
|
player.currentTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seeking
|
||||||
|
let { currentTime } = player.media;
|
||||||
|
Object.defineProperty(player.media, 'currentTime', {
|
||||||
|
get() {
|
||||||
|
return currentTime;
|
||||||
|
},
|
||||||
|
set(time) {
|
||||||
|
// Vimeo will automatically play on seek if the video hasn't been played before
|
||||||
|
|
||||||
|
// Get current paused state and volume etc
|
||||||
|
const { embed, media, paused, volume } = player;
|
||||||
|
const restorePause = paused && !embed.hasPlayed;
|
||||||
|
|
||||||
|
// Set seeking state and trigger event
|
||||||
|
media.seeking = true;
|
||||||
|
triggerEvent.call(player, media, 'seeking');
|
||||||
|
|
||||||
|
// If paused, mute until seek is complete
|
||||||
|
Promise.resolve(restorePause && embed.setVolume(0))
|
||||||
|
// Seek
|
||||||
|
.then(() => embed.setCurrentTime(time))
|
||||||
|
// Restore paused
|
||||||
|
.then(() => restorePause && embed.pause())
|
||||||
|
// Restore volume
|
||||||
|
.then(() => restorePause && embed.setVolume(volume))
|
||||||
|
.catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Playback speed
|
||||||
|
let speed = player.config.speed.selected;
|
||||||
|
Object.defineProperty(player.media, 'playbackRate', {
|
||||||
|
get() {
|
||||||
|
return speed;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
player.embed
|
player.embed
|
||||||
.getVideoUrl()
|
.setPlaybackRate(input)
|
||||||
.then(value => {
|
.then(() => {
|
||||||
currentSrc = value;
|
speed = input;
|
||||||
controls.setDownloadUrl.call(player);
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(() => {
|
||||||
this.debug.warn(error);
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
});
|
player.options.speed = [1];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(player.media, 'currentSrc', {
|
// Volume
|
||||||
get() {
|
let { volume } = player.config;
|
||||||
return currentSrc;
|
Object.defineProperty(player.media, 'volume', {
|
||||||
},
|
get() {
|
||||||
|
return volume;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
player.embed.setVolume(input).then(() => {
|
||||||
|
volume = input;
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Ended
|
// Muted
|
||||||
Object.defineProperty(player.media, 'ended', {
|
let { muted } = player.config;
|
||||||
get() {
|
Object.defineProperty(player.media, 'muted', {
|
||||||
return player.currentTime === player.duration;
|
get() {
|
||||||
},
|
return muted;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : false;
|
||||||
|
|
||||||
|
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
||||||
|
muted = toggle;
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Set aspect ratio based on video size
|
// Loop
|
||||||
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
let { loop } = player.config;
|
||||||
const [width, height] = dimensions;
|
Object.defineProperty(player.media, 'loop', {
|
||||||
player.embed.ratio = [width, height];
|
get() {
|
||||||
setAspectRatio.call(this);
|
return loop;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : player.config.loop.active;
|
||||||
|
|
||||||
|
player.embed.setLoop(toggle).then(() => {
|
||||||
|
loop = toggle;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Set autopause
|
// Source
|
||||||
player.embed.setAutopause(player.config.autopause).then(state => {
|
let currentSrc;
|
||||||
player.config.autopause = state;
|
player.embed
|
||||||
});
|
.getVideoUrl()
|
||||||
|
.then(value => {
|
||||||
|
currentSrc = value;
|
||||||
|
controls.setDownloadUrl.call(player);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.debug.warn(error);
|
||||||
|
});
|
||||||
|
|
||||||
// Get title
|
Object.defineProperty(player.media, 'currentSrc', {
|
||||||
player.embed.getVideoTitle().then(title => {
|
get() {
|
||||||
player.config.title = title;
|
return currentSrc;
|
||||||
ui.setTitle.call(this);
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get current time
|
// Ended
|
||||||
player.embed.getCurrentTime().then(value => {
|
Object.defineProperty(player.media, 'ended', {
|
||||||
currentTime = value;
|
get() {
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
return player.currentTime === player.duration;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Get duration
|
// Set aspect ratio based on video size
|
||||||
player.embed.getDuration().then(value => {
|
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
||||||
player.media.duration = value;
|
const [width, height] = dimensions;
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
player.embed.ratio = [width, height];
|
||||||
});
|
setAspectRatio.call(this);
|
||||||
|
});
|
||||||
|
|
||||||
// Get captions
|
// Set autopause
|
||||||
player.embed.getTextTracks().then(tracks => {
|
player.embed.setAutopause(player.config.autopause).then(state => {
|
||||||
player.media.textTracks = tracks;
|
player.config.autopause = state;
|
||||||
captions.setup.call(player);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('cuechange', ({ cues = [] }) => {
|
// Get title
|
||||||
const strippedCues = cues.map(cue => stripHTML(cue.text));
|
player.embed.getVideoTitle().then(title => {
|
||||||
captions.updateCues.call(player, strippedCues);
|
player.config.title = title;
|
||||||
});
|
ui.setTitle.call(this);
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('loaded', () => {
|
// Get current time
|
||||||
// Assure state and events are updated on autoplay
|
player.embed.getCurrentTime().then(value => {
|
||||||
player.embed.getPaused().then(paused => {
|
currentTime = value;
|
||||||
assurePlaybackState.call(player, !paused);
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
if (!paused) {
|
});
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is.element(player.embed.element) && player.supported.ui) {
|
// Get duration
|
||||||
const frame = player.embed.element;
|
player.embed.getDuration().then(value => {
|
||||||
|
player.media.duration = value;
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
});
|
||||||
|
|
||||||
// Fix keyboard focus issues
|
// Get captions
|
||||||
// https://github.com/sampotts/plyr/issues/317
|
player.embed.getTextTracks().then(tracks => {
|
||||||
frame.setAttribute('tabindex', -1);
|
player.media.textTracks = tracks;
|
||||||
}
|
captions.setup.call(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
player.embed.on('bufferstart', () => {
|
player.embed.on('cuechange', ({ cues = [] }) => {
|
||||||
triggerEvent.call(player, player.media, 'waiting');
|
const strippedCues = cues.map(cue => stripHTML(cue.text));
|
||||||
});
|
captions.updateCues.call(player, strippedCues);
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('bufferend', () => {
|
player.embed.on('loaded', () => {
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
// Assure state and events are updated on autoplay
|
||||||
});
|
player.embed.getPaused().then(paused => {
|
||||||
|
assurePlaybackState.call(player, !paused);
|
||||||
|
if (!paused) {
|
||||||
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('play', () => {
|
if (is.element(player.embed.element) && player.supported.ui) {
|
||||||
assurePlaybackState.call(player, true);
|
const frame = player.embed.element;
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('pause', () => {
|
// Fix keyboard focus issues
|
||||||
assurePlaybackState.call(player, false);
|
// https://github.com/sampotts/plyr/issues/317
|
||||||
});
|
frame.setAttribute('tabindex', -1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('timeupdate', data => {
|
player.embed.on('bufferstart', () => {
|
||||||
player.media.seeking = false;
|
triggerEvent.call(player, player.media, 'waiting');
|
||||||
currentTime = data.seconds;
|
});
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('progress', data => {
|
player.embed.on('bufferend', () => {
|
||||||
player.media.buffered = data.percent;
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
});
|
||||||
|
|
||||||
// Check all loaded
|
player.embed.on('play', () => {
|
||||||
if (parseInt(data.percent, 10) === 1) {
|
assurePlaybackState.call(player, true);
|
||||||
triggerEvent.call(player, player.media, 'canplaythrough');
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
}
|
});
|
||||||
|
|
||||||
// Get duration as if we do it before load, it gives an incorrect value
|
player.embed.on('pause', () => {
|
||||||
// https://github.com/sampotts/plyr/issues/891
|
assurePlaybackState.call(player, false);
|
||||||
player.embed.getDuration().then(value => {
|
});
|
||||||
if (value !== player.media.duration) {
|
|
||||||
player.media.duration = value;
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('seeked', () => {
|
player.embed.on('timeupdate', data => {
|
||||||
player.media.seeking = false;
|
player.media.seeking = false;
|
||||||
triggerEvent.call(player, player.media, 'seeked');
|
currentTime = data.seconds;
|
||||||
});
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('ended', () => {
|
player.embed.on('progress', data => {
|
||||||
player.media.paused = true;
|
player.media.buffered = data.percent;
|
||||||
triggerEvent.call(player, player.media, 'ended');
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('error', detail => {
|
// Check all loaded
|
||||||
player.media.error = detail;
|
if (parseInt(data.percent, 10) === 1) {
|
||||||
triggerEvent.call(player, player.media, 'error');
|
triggerEvent.call(player, player.media, 'canplaythrough');
|
||||||
});
|
}
|
||||||
|
|
||||||
// Rebuild UI
|
// Get duration as if we do it before load, it gives an incorrect value
|
||||||
setTimeout(() => ui.build.call(player), 0);
|
// https://github.com/sampotts/plyr/issues/891
|
||||||
},
|
player.embed.getDuration().then(value => {
|
||||||
|
if (value !== player.media.duration) {
|
||||||
|
player.media.duration = value;
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('seeked', () => {
|
||||||
|
player.media.seeking = false;
|
||||||
|
triggerEvent.call(player, player.media, 'seeked');
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('ended', () => {
|
||||||
|
player.media.paused = true;
|
||||||
|
triggerEvent.call(player, player.media, 'ended');
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('error', detail => {
|
||||||
|
player.media.error = detail;
|
||||||
|
triggerEvent.call(player, player.media, 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rebuild UI
|
||||||
|
setTimeout(() => ui.build.call(player), 0);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default vimeo;
|
export default vimeo;
|
||||||
|
|||||||
+390
-390
@@ -15,426 +15,426 @@ import { setAspectRatio } from '../utils/style';
|
|||||||
|
|
||||||
// Parse YouTube ID from URL
|
// Parse YouTube ID from URL
|
||||||
function parseId(url) {
|
function parseId(url) {
|
||||||
if (is.empty(url)) {
|
if (is.empty(url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
||||||
return url.match(regex) ? RegExp.$2 : url;
|
return url.match(regex) ? RegExp.$2 : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set playback state and trigger change (only on actual change)
|
// Set playback state and trigger change (only on actual change)
|
||||||
function assurePlaybackState(play) {
|
function assurePlaybackState(play) {
|
||||||
if (play && !this.embed.hasPlayed) {
|
if (play && !this.embed.hasPlayed) {
|
||||||
this.embed.hasPlayed = true;
|
this.embed.hasPlayed = true;
|
||||||
}
|
}
|
||||||
if (this.media.paused === play) {
|
if (this.media.paused === play) {
|
||||||
this.media.paused = !play;
|
this.media.paused = !play;
|
||||||
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHost(config) {
|
function getHost(config) {
|
||||||
if (config.noCookie) {
|
if (config.noCookie) {
|
||||||
return 'https://www.youtube-nocookie.com';
|
return 'https://www.youtube-nocookie.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.protocol === 'http:') {
|
if (window.location.protocol === 'http:') {
|
||||||
return 'http://www.youtube.com';
|
return 'http://www.youtube.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use YouTube's default
|
// Use YouTube's default
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const youtube = {
|
const youtube = {
|
||||||
setup() {
|
setup() {
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||||
|
|
||||||
// Setup API
|
// Setup API
|
||||||
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
||||||
youtube.ready.call(this);
|
youtube.ready.call(this);
|
||||||
} else {
|
} else {
|
||||||
// Reference current global callback
|
// Reference current global callback
|
||||||
const callback = window.onYouTubeIframeAPIReady;
|
const callback = window.onYouTubeIframeAPIReady;
|
||||||
|
|
||||||
// Set callback to process queue
|
// Set callback to process queue
|
||||||
window.onYouTubeIframeAPIReady = () => {
|
window.onYouTubeIframeAPIReady = () => {
|
||||||
// Call global callback if set
|
// Call global callback if set
|
||||||
if (is.function(callback)) {
|
if (is.function(callback)) {
|
||||||
callback();
|
callback();
|
||||||
}
|
|
||||||
|
|
||||||
youtube.ready.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load the SDK
|
|
||||||
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
|
||||||
this.debug.warn('YouTube API failed to load', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Get the media title
|
youtube.ready.call(this);
|
||||||
getTitle(videoId) {
|
};
|
||||||
const url = format(this.config.urls.youtube.api, videoId);
|
|
||||||
|
|
||||||
fetch(url)
|
// Load the SDK
|
||||||
.then(data => {
|
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
||||||
if (is.object(data)) {
|
this.debug.warn('YouTube API failed to load', error);
|
||||||
const { title, height, width } = data;
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Set title
|
// Get the media title
|
||||||
this.config.title = title;
|
getTitle(videoId) {
|
||||||
ui.setTitle.call(this);
|
const url = format(this.config.urls.youtube.api, videoId);
|
||||||
|
|
||||||
// Set aspect ratio
|
fetch(url)
|
||||||
this.embed.ratio = [width, height];
|
.then(data => {
|
||||||
}
|
if (is.object(data)) {
|
||||||
|
const { title, height, width } = data;
|
||||||
|
|
||||||
setAspectRatio.call(this);
|
// Set title
|
||||||
})
|
this.config.title = title;
|
||||||
.catch(() => {
|
ui.setTitle.call(this);
|
||||||
// Set aspect ratio
|
|
||||||
setAspectRatio.call(this);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// API ready
|
// Set aspect ratio
|
||||||
ready() {
|
this.embed.ratio = [width, height];
|
||||||
const player = this;
|
}
|
||||||
// Ignore already setup (race condition)
|
|
||||||
const currentId = player.media && player.media.getAttribute('id');
|
setAspectRatio.call(this);
|
||||||
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Set aspect ratio
|
||||||
|
setAspectRatio.call(this);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// API ready
|
||||||
|
ready() {
|
||||||
|
const player = this;
|
||||||
|
// Ignore already setup (race condition)
|
||||||
|
const currentId = player.media && player.media.getAttribute('id');
|
||||||
|
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source URL or ID
|
||||||
|
let source = player.media.getAttribute('src');
|
||||||
|
|
||||||
|
// Get from <div> if needed
|
||||||
|
if (is.empty(source)) {
|
||||||
|
source = player.media.getAttribute(this.config.attributes.embed.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the <iframe> with a <div> due to YouTube API issues
|
||||||
|
const videoId = parseId(source);
|
||||||
|
const id = generateId(player.provider);
|
||||||
|
// Get poster, if already set
|
||||||
|
const { poster } = player;
|
||||||
|
// Replace media element
|
||||||
|
const container = createElement('div', { id, poster });
|
||||||
|
player.media = replaceElement(container, player.media);
|
||||||
|
|
||||||
|
// Id to poster wrapper
|
||||||
|
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(src => {
|
||||||
|
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
||||||
|
if (!src.includes('maxres')) {
|
||||||
|
player.elements.poster.style.backgroundSize = 'cover';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
const config = player.config.youtube;
|
||||||
|
|
||||||
|
// Setup instance
|
||||||
|
// https://developers.google.com/youtube/iframe_api_reference
|
||||||
|
player.embed = new window.YT.Player(id, {
|
||||||
|
videoId,
|
||||||
|
host: getHost(config),
|
||||||
|
playerVars: extend(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
|
||||||
|
hl: player.config.hl, // iframe interface language
|
||||||
|
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
|
||||||
|
disablekb: 1, // Disable keyboard as we handle it
|
||||||
|
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
|
||||||
|
// Captions are flaky on YouTube
|
||||||
|
cc_load_policy: player.captions.active ? 1 : 0,
|
||||||
|
cc_lang_pref: player.config.captions.language,
|
||||||
|
// Tracking for stats
|
||||||
|
widget_referrer: window ? window.location.href : null,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
),
|
||||||
|
events: {
|
||||||
|
onError(event) {
|
||||||
|
// YouTube may fire onError twice, so only handle it once
|
||||||
|
if (!player.media.error) {
|
||||||
|
const code = event.data;
|
||||||
|
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
||||||
|
const message =
|
||||||
|
{
|
||||||
|
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
|
||||||
|
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
|
||||||
|
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
|
||||||
|
101: 'The owner of the requested video does not allow it to be played in embedded players.',
|
||||||
|
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
||||||
|
}[code] || 'An unknown error occured';
|
||||||
|
|
||||||
|
player.media.error = { code, message };
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPlaybackRateChange(event) {
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
|
// Get current speed
|
||||||
|
player.media.playbackRate = instance.getPlaybackRate();
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
},
|
||||||
|
onReady(event) {
|
||||||
|
// Bail if onReady has already been called. See issue #1108
|
||||||
|
if (is.function(player.media.play)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
// Get the source URL or ID
|
// Get the title
|
||||||
let source = player.media.getAttribute('src');
|
youtube.getTitle.call(player, videoId);
|
||||||
|
|
||||||
// Get from <div> if needed
|
// Create a faux HTML5 API using the YouTube API
|
||||||
if (is.empty(source)) {
|
player.media.play = () => {
|
||||||
source = player.media.getAttribute(this.config.attributes.embed.id);
|
assurePlaybackState.call(player, true);
|
||||||
}
|
instance.playVideo();
|
||||||
|
};
|
||||||
|
|
||||||
// Replace the <iframe> with a <div> due to YouTube API issues
|
player.media.pause = () => {
|
||||||
const videoId = parseId(source);
|
assurePlaybackState.call(player, false);
|
||||||
const id = generateId(player.provider);
|
instance.pauseVideo();
|
||||||
// Get poster, if already set
|
};
|
||||||
const { poster } = player;
|
|
||||||
// Replace media element
|
|
||||||
const container = createElement('div', { id, poster });
|
|
||||||
player.media = replaceElement(container, player.media);
|
|
||||||
|
|
||||||
// Id to poster wrapper
|
player.media.stop = () => {
|
||||||
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
instance.stopVideo();
|
||||||
|
};
|
||||||
|
|
||||||
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
player.media.duration = instance.getDuration();
|
||||||
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
player.media.paused = true;
|
||||||
.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(src => {
|
|
||||||
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
|
||||||
if (!src.includes('maxres')) {
|
|
||||||
player.elements.poster.style.backgroundSize = 'cover';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
const config = player.config.youtube;
|
// Seeking
|
||||||
|
player.media.currentTime = 0;
|
||||||
// Setup instance
|
Object.defineProperty(player.media, 'currentTime', {
|
||||||
// https://developers.google.com/youtube/iframe_api_reference
|
get() {
|
||||||
player.embed = new window.YT.Player(id, {
|
return Number(instance.getCurrentTime());
|
||||||
videoId,
|
|
||||||
host: getHost(config),
|
|
||||||
playerVars: extend(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
|
|
||||||
hl: player.config.hl, // iframe interface language
|
|
||||||
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
|
|
||||||
disablekb: 1, // Disable keyboard as we handle it
|
|
||||||
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
|
|
||||||
// Captions are flaky on YouTube
|
|
||||||
cc_load_policy: player.captions.active ? 1 : 0,
|
|
||||||
cc_lang_pref: player.config.captions.language,
|
|
||||||
// Tracking for stats
|
|
||||||
widget_referrer: window ? window.location.href : null,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
events: {
|
|
||||||
onError(event) {
|
|
||||||
// YouTube may fire onError twice, so only handle it once
|
|
||||||
if (!player.media.error) {
|
|
||||||
const code = event.data;
|
|
||||||
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
|
||||||
const message =
|
|
||||||
{
|
|
||||||
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
|
|
||||||
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
|
|
||||||
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
|
|
||||||
101: 'The owner of the requested video does not allow it to be played in embedded players.',
|
|
||||||
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
|
||||||
}[code] || 'An unknown error occured';
|
|
||||||
|
|
||||||
player.media.error = { code, message };
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPlaybackRateChange(event) {
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Get current speed
|
|
||||||
player.media.playbackRate = instance.getPlaybackRate();
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
|
||||||
},
|
|
||||||
onReady(event) {
|
|
||||||
// Bail if onReady has already been called. See issue #1108
|
|
||||||
if (is.function(player.media.play)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Get the title
|
|
||||||
youtube.getTitle.call(player, videoId);
|
|
||||||
|
|
||||||
// Create a faux HTML5 API using the YouTube API
|
|
||||||
player.media.play = () => {
|
|
||||||
assurePlaybackState.call(player, true);
|
|
||||||
instance.playVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.pause = () => {
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
instance.pauseVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.stop = () => {
|
|
||||||
instance.stopVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.duration = instance.getDuration();
|
|
||||||
player.media.paused = true;
|
|
||||||
|
|
||||||
// Seeking
|
|
||||||
player.media.currentTime = 0;
|
|
||||||
Object.defineProperty(player.media, 'currentTime', {
|
|
||||||
get() {
|
|
||||||
return Number(instance.getCurrentTime());
|
|
||||||
},
|
|
||||||
set(time) {
|
|
||||||
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
|
||||||
if (player.paused && !player.embed.hasPlayed) {
|
|
||||||
player.embed.mute();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set seeking state and trigger event
|
|
||||||
player.media.seeking = true;
|
|
||||||
triggerEvent.call(player, player.media, 'seeking');
|
|
||||||
|
|
||||||
// Seek after events sent
|
|
||||||
instance.seekTo(time);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Playback speed
|
|
||||||
Object.defineProperty(player.media, 'playbackRate', {
|
|
||||||
get() {
|
|
||||||
return instance.getPlaybackRate();
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
instance.setPlaybackRate(input);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume
|
|
||||||
let { volume } = player.config;
|
|
||||||
Object.defineProperty(player.media, 'volume', {
|
|
||||||
get() {
|
|
||||||
return volume;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
volume = input;
|
|
||||||
instance.setVolume(volume * 100);
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Muted
|
|
||||||
let { muted } = player.config;
|
|
||||||
Object.defineProperty(player.media, 'muted', {
|
|
||||||
get() {
|
|
||||||
return muted;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : muted;
|
|
||||||
muted = toggle;
|
|
||||||
instance[toggle ? 'mute' : 'unMute']();
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Source
|
|
||||||
Object.defineProperty(player.media, 'currentSrc', {
|
|
||||||
get() {
|
|
||||||
return instance.getVideoUrl();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ended
|
|
||||||
Object.defineProperty(player.media, 'ended', {
|
|
||||||
get() {
|
|
||||||
return player.currentTime === player.duration;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get available speeds
|
|
||||||
const speeds = instance.getAvailablePlaybackRates();
|
|
||||||
// Filter based on config
|
|
||||||
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
|
||||||
|
|
||||||
// Set the tabindex to avoid focus entering iframe
|
|
||||||
if (player.supported.ui) {
|
|
||||||
player.media.setAttribute('tabindex', -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
|
|
||||||
// Reset timer
|
|
||||||
clearInterval(player.timers.buffering);
|
|
||||||
|
|
||||||
// Setup buffering
|
|
||||||
player.timers.buffering = setInterval(() => {
|
|
||||||
// Get loaded % from YouTube
|
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
|
||||||
|
|
||||||
// Trigger progress only when we actually buffer something
|
|
||||||
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set last buffer point
|
|
||||||
player.media.lastBuffered = player.media.buffered;
|
|
||||||
|
|
||||||
// Bail if we're at 100%
|
|
||||||
if (player.media.buffered === 1) {
|
|
||||||
clearInterval(player.timers.buffering);
|
|
||||||
|
|
||||||
// Trigger event
|
|
||||||
triggerEvent.call(player, player.media, 'canplaythrough');
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
// Rebuild UI
|
|
||||||
setTimeout(() => ui.build.call(player), 50);
|
|
||||||
},
|
|
||||||
onStateChange(event) {
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Reset timer
|
|
||||||
clearInterval(player.timers.playing);
|
|
||||||
|
|
||||||
const seeked = player.media.seeking && [1, 2].includes(event.data);
|
|
||||||
|
|
||||||
if (seeked) {
|
|
||||||
// Unset seeking and fire seeked event
|
|
||||||
player.media.seeking = false;
|
|
||||||
triggerEvent.call(player, player.media, 'seeked');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle events
|
|
||||||
// -1 Unstarted
|
|
||||||
// 0 Ended
|
|
||||||
// 1 Playing
|
|
||||||
// 2 Paused
|
|
||||||
// 3 Buffering
|
|
||||||
// 5 Video cued
|
|
||||||
switch (event.data) {
|
|
||||||
case -1:
|
|
||||||
// Update scrubber
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
|
|
||||||
// Get loaded % from YouTube
|
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
|
|
||||||
// YouTube doesn't support loop for a single video, so mimick it.
|
|
||||||
if (player.media.loop) {
|
|
||||||
// YouTube needs a call to `stopVideo` before playing again
|
|
||||||
instance.stopVideo();
|
|
||||||
instance.playVideo();
|
|
||||||
} else {
|
|
||||||
triggerEvent.call(player, player.media, 'ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
|
||||||
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
|
||||||
player.media.pause();
|
|
||||||
} else {
|
|
||||||
assurePlaybackState.call(player, true);
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
|
|
||||||
// Poll to get playback progress
|
|
||||||
player.timers.playing = setInterval(() => {
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
// Check duration again due to YouTube bug
|
|
||||||
// https://github.com/sampotts/plyr/issues/374
|
|
||||||
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
|
||||||
if (player.media.duration !== instance.getDuration()) {
|
|
||||||
player.media.duration = instance.getDuration();
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
|
|
||||||
if (!player.muted) {
|
|
||||||
player.embed.unMute();
|
|
||||||
}
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
// Trigger waiting event to add loading classes to container as the video buffers.
|
|
||||||
triggerEvent.call(player, player.media, 'waiting');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.elements.container, 'statechange', false, {
|
|
||||||
code: event.data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
set(time) {
|
||||||
},
|
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
||||||
|
if (player.paused && !player.embed.hasPlayed) {
|
||||||
|
player.embed.mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set seeking state and trigger event
|
||||||
|
player.media.seeking = true;
|
||||||
|
triggerEvent.call(player, player.media, 'seeking');
|
||||||
|
|
||||||
|
// Seek after events sent
|
||||||
|
instance.seekTo(time);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Playback speed
|
||||||
|
Object.defineProperty(player.media, 'playbackRate', {
|
||||||
|
get() {
|
||||||
|
return instance.getPlaybackRate();
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
instance.setPlaybackRate(input);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
let { volume } = player.config;
|
||||||
|
Object.defineProperty(player.media, 'volume', {
|
||||||
|
get() {
|
||||||
|
return volume;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
volume = input;
|
||||||
|
instance.setVolume(volume * 100);
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Muted
|
||||||
|
let { muted } = player.config;
|
||||||
|
Object.defineProperty(player.media, 'muted', {
|
||||||
|
get() {
|
||||||
|
return muted;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : muted;
|
||||||
|
muted = toggle;
|
||||||
|
instance[toggle ? 'mute' : 'unMute']();
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Source
|
||||||
|
Object.defineProperty(player.media, 'currentSrc', {
|
||||||
|
get() {
|
||||||
|
return instance.getVideoUrl();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ended
|
||||||
|
Object.defineProperty(player.media, 'ended', {
|
||||||
|
get() {
|
||||||
|
return player.currentTime === player.duration;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get available speeds
|
||||||
|
const speeds = instance.getAvailablePlaybackRates();
|
||||||
|
// Filter based on config
|
||||||
|
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
||||||
|
|
||||||
|
// Set the tabindex to avoid focus entering iframe
|
||||||
|
if (player.supported.ui) {
|
||||||
|
player.media.setAttribute('tabindex', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
|
||||||
|
// Reset timer
|
||||||
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
|
// Setup buffering
|
||||||
|
player.timers.buffering = setInterval(() => {
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
|
||||||
|
// Trigger progress only when we actually buffer something
|
||||||
|
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
||||||
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set last buffer point
|
||||||
|
player.media.lastBuffered = player.media.buffered;
|
||||||
|
|
||||||
|
// Bail if we're at 100%
|
||||||
|
if (player.media.buffered === 1) {
|
||||||
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
|
// Trigger event
|
||||||
|
triggerEvent.call(player, player.media, 'canplaythrough');
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
// Rebuild UI
|
||||||
|
setTimeout(() => ui.build.call(player), 50);
|
||||||
|
},
|
||||||
|
onStateChange(event) {
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
|
// Reset timer
|
||||||
|
clearInterval(player.timers.playing);
|
||||||
|
|
||||||
|
const seeked = player.media.seeking && [1, 2].includes(event.data);
|
||||||
|
|
||||||
|
if (seeked) {
|
||||||
|
// Unset seeking and fire seeked event
|
||||||
|
player.media.seeking = false;
|
||||||
|
triggerEvent.call(player, player.media, 'seeked');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle events
|
||||||
|
// -1 Unstarted
|
||||||
|
// 0 Ended
|
||||||
|
// 1 Playing
|
||||||
|
// 2 Paused
|
||||||
|
// 3 Buffering
|
||||||
|
// 5 Video cued
|
||||||
|
switch (event.data) {
|
||||||
|
case -1:
|
||||||
|
// Update scrubber
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
assurePlaybackState.call(player, false);
|
||||||
|
|
||||||
|
// YouTube doesn't support loop for a single video, so mimick it.
|
||||||
|
if (player.media.loop) {
|
||||||
|
// YouTube needs a call to `stopVideo` before playing again
|
||||||
|
instance.stopVideo();
|
||||||
|
instance.playVideo();
|
||||||
|
} else {
|
||||||
|
triggerEvent.call(player, player.media, 'ended');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
||||||
|
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
||||||
|
player.media.pause();
|
||||||
|
} else {
|
||||||
|
assurePlaybackState.call(player, true);
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
|
|
||||||
|
// Poll to get playback progress
|
||||||
|
player.timers.playing = setInterval(() => {
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// Check duration again due to YouTube bug
|
||||||
|
// https://github.com/sampotts/plyr/issues/374
|
||||||
|
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
||||||
|
if (player.media.duration !== instance.getDuration()) {
|
||||||
|
player.media.duration = instance.getDuration();
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
|
||||||
|
if (!player.muted) {
|
||||||
|
player.embed.unMute();
|
||||||
|
}
|
||||||
|
assurePlaybackState.call(player, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
// Trigger waiting event to add loading classes to container as the video buffers.
|
||||||
|
triggerEvent.call(player, player.media, 'waiting');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.elements.container, 'statechange', false, {
|
||||||
|
code: event.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default youtube;
|
export default youtube;
|
||||||
|
|||||||
Vendored
+493
-493
File diff suppressed because it is too large
Load Diff
+1054
-1054
File diff suppressed because it is too large
Load Diff
+122
-122
@@ -13,146 +13,146 @@ import is from './utils/is';
|
|||||||
import { getDeep } from './utils/objects';
|
import { getDeep } from './utils/objects';
|
||||||
|
|
||||||
const source = {
|
const source = {
|
||||||
// Add elements to HTML5 media (source, tracks, etc)
|
// Add elements to HTML5 media (source, tracks, etc)
|
||||||
insertElements(type, attributes) {
|
insertElements(type, attributes) {
|
||||||
if (is.string(attributes)) {
|
if (is.string(attributes)) {
|
||||||
insertElement(type, this.media, {
|
insertElement(type, this.media, {
|
||||||
src: attributes,
|
src: attributes,
|
||||||
});
|
});
|
||||||
} else if (is.array(attributes)) {
|
} else if (is.array(attributes)) {
|
||||||
attributes.forEach(attribute => {
|
attributes.forEach(attribute => {
|
||||||
insertElement(type, this.media, attribute);
|
insertElement(type, this.media, attribute);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update source
|
// Update source
|
||||||
// Sources are not checked for support so be careful
|
// Sources are not checked for support so be careful
|
||||||
change(input) {
|
change(input) {
|
||||||
if (!getDeep(input, 'sources.length')) {
|
if (!getDeep(input, 'sources.length')) {
|
||||||
this.debug.warn('Invalid source format');
|
this.debug.warn('Invalid source format');
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel current network requests
|
||||||
|
html5.cancelRequests.call(this);
|
||||||
|
|
||||||
|
// Destroy instance and re-setup
|
||||||
|
this.destroy.call(
|
||||||
|
this,
|
||||||
|
() => {
|
||||||
|
// Reset quality options
|
||||||
|
this.options.quality = [];
|
||||||
|
|
||||||
|
// Remove elements
|
||||||
|
removeElement(this.media);
|
||||||
|
this.media = null;
|
||||||
|
|
||||||
|
// Reset class name
|
||||||
|
if (is.element(this.elements.container)) {
|
||||||
|
this.elements.container.removeAttribute('class');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel current network requests
|
// Set the type and provider
|
||||||
html5.cancelRequests.call(this);
|
const { sources, type } = input;
|
||||||
|
const [{ provider = providers.html5, src }] = sources;
|
||||||
|
const tagName = provider === 'html5' ? type : 'div';
|
||||||
|
const attributes = provider === 'html5' ? {} : { src };
|
||||||
|
|
||||||
// Destroy instance and re-setup
|
Object.assign(this, {
|
||||||
this.destroy.call(
|
provider,
|
||||||
this,
|
type,
|
||||||
() => {
|
// Check for support
|
||||||
// Reset quality options
|
supported: support.check(type, provider, this.config.playsinline),
|
||||||
this.options.quality = [];
|
// Create new element
|
||||||
|
media: createElement(tagName, attributes),
|
||||||
|
});
|
||||||
|
|
||||||
// Remove elements
|
// Inject the new element
|
||||||
removeElement(this.media);
|
this.elements.container.appendChild(this.media);
|
||||||
this.media = null;
|
|
||||||
|
|
||||||
// Reset class name
|
// Autoplay the new source?
|
||||||
if (is.element(this.elements.container)) {
|
if (is.boolean(input.autoplay)) {
|
||||||
this.elements.container.removeAttribute('class');
|
this.config.autoplay = input.autoplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the type and provider
|
// Set attributes for audio and video
|
||||||
const { sources, type } = input;
|
if (this.isHTML5) {
|
||||||
const [{ provider = providers.html5, src }] = sources;
|
if (this.config.crossorigin) {
|
||||||
const tagName = provider === 'html5' ? type : 'div';
|
this.media.setAttribute('crossorigin', '');
|
||||||
const attributes = provider === 'html5' ? {} : { src };
|
}
|
||||||
|
if (this.config.autoplay) {
|
||||||
|
this.media.setAttribute('autoplay', '');
|
||||||
|
}
|
||||||
|
if (!is.empty(input.poster)) {
|
||||||
|
this.poster = input.poster;
|
||||||
|
}
|
||||||
|
if (this.config.loop.active) {
|
||||||
|
this.media.setAttribute('loop', '');
|
||||||
|
}
|
||||||
|
if (this.config.muted) {
|
||||||
|
this.media.setAttribute('muted', '');
|
||||||
|
}
|
||||||
|
if (this.config.playsinline) {
|
||||||
|
this.media.setAttribute('playsinline', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, {
|
// Restore class hook
|
||||||
provider,
|
ui.addStyleHook.call(this);
|
||||||
type,
|
|
||||||
// Check for support
|
|
||||||
supported: support.check(type, provider, this.config.playsinline),
|
|
||||||
// Create new element
|
|
||||||
media: createElement(tagName, attributes),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inject the new element
|
// Set new sources for html5
|
||||||
this.elements.container.appendChild(this.media);
|
if (this.isHTML5) {
|
||||||
|
source.insertElements.call(this, 'source', sources);
|
||||||
|
}
|
||||||
|
|
||||||
// Autoplay the new source?
|
// Set video title
|
||||||
if (is.boolean(input.autoplay)) {
|
this.config.title = input.title;
|
||||||
this.config.autoplay = input.autoplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set attributes for audio and video
|
// Set up from scratch
|
||||||
if (this.isHTML5) {
|
media.setup.call(this);
|
||||||
if (this.config.crossorigin) {
|
|
||||||
this.media.setAttribute('crossorigin', '');
|
|
||||||
}
|
|
||||||
if (this.config.autoplay) {
|
|
||||||
this.media.setAttribute('autoplay', '');
|
|
||||||
}
|
|
||||||
if (!is.empty(input.poster)) {
|
|
||||||
this.poster = input.poster;
|
|
||||||
}
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.media.setAttribute('loop', '');
|
|
||||||
}
|
|
||||||
if (this.config.muted) {
|
|
||||||
this.media.setAttribute('muted', '');
|
|
||||||
}
|
|
||||||
if (this.config.playsinline) {
|
|
||||||
this.media.setAttribute('playsinline', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore class hook
|
// HTML5 stuff
|
||||||
ui.addStyleHook.call(this);
|
if (this.isHTML5) {
|
||||||
|
// Setup captions
|
||||||
|
if (Object.keys(input).includes('tracks')) {
|
||||||
|
source.insertElements.call(this, 'track', input.tracks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set new sources for html5
|
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
||||||
source.insertElements.call(this, 'source', sources);
|
// Setup interface
|
||||||
}
|
ui.build.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Set video title
|
// Load HTML5 sources
|
||||||
this.config.title = input.title;
|
if (this.isHTML5) {
|
||||||
|
this.media.load();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up from scratch
|
// Update previewThumbnails config & reload plugin
|
||||||
media.setup.call(this);
|
if (!is.empty(input.previewThumbnails)) {
|
||||||
|
Object.assign(this.config.previewThumbnails, input.previewThumbnails);
|
||||||
|
|
||||||
// HTML5 stuff
|
// Cleanup previewThumbnails plugin if it was loaded
|
||||||
if (this.isHTML5) {
|
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
||||||
// Setup captions
|
this.previewThumbnails.destroy();
|
||||||
if (Object.keys(input).includes('tracks')) {
|
this.previewThumbnails = null;
|
||||||
source.insertElements.call(this, 'track', input.tracks);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
// Create new instance if it is still enabled
|
||||||
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
if (this.config.previewThumbnails.enabled) {
|
||||||
// Setup interface
|
this.previewThumbnails = new PreviewThumbnails(this);
|
||||||
ui.build.call(this);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load HTML5 sources
|
// Update the fullscreen support
|
||||||
if (this.isHTML5) {
|
this.fullscreen.update();
|
||||||
this.media.load();
|
},
|
||||||
}
|
true,
|
||||||
|
);
|
||||||
// Update previewThumbnails config & reload plugin
|
},
|
||||||
if (!is.empty(input.previewThumbnails)) {
|
|
||||||
Object.assign(this.config.previewThumbnails, input.previewThumbnails);
|
|
||||||
|
|
||||||
// Cleanup previewThumbnails plugin if it was loaded
|
|
||||||
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
|
||||||
this.previewThumbnails.destroy();
|
|
||||||
this.previewThumbnails = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new instance if it is still enabled
|
|
||||||
if (this.config.previewThumbnails.enabled) {
|
|
||||||
this.previewThumbnails = new PreviewThumbnails(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the fullscreen support
|
|
||||||
this.fullscreen.update();
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default source;
|
export default source;
|
||||||
|
|||||||
+56
-56
@@ -6,72 +6,72 @@ import is from './utils/is';
|
|||||||
import { extend } from './utils/objects';
|
import { extend } from './utils/objects';
|
||||||
|
|
||||||
class Storage {
|
class Storage {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.enabled = player.config.storage.enabled;
|
this.enabled = player.config.storage.enabled;
|
||||||
this.key = player.config.storage.key;
|
this.key = player.config.storage.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for actual support (see if we can use it)
|
||||||
|
static get supported() {
|
||||||
|
try {
|
||||||
|
if (!('localStorage' in window)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = '___test';
|
||||||
|
|
||||||
|
// Try to use it (it might be disabled, e.g. user is in private mode)
|
||||||
|
// see: https://github.com/sampotts/plyr/issues/131
|
||||||
|
window.localStorage.setItem(test, test);
|
||||||
|
window.localStorage.removeItem(test);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
if (!Storage.supported || !this.enabled) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for actual support (see if we can use it)
|
const store = window.localStorage.getItem(this.key);
|
||||||
static get supported() {
|
|
||||||
try {
|
|
||||||
if (!('localStorage' in window)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = '___test';
|
if (is.empty(store)) {
|
||||||
|
return null;
|
||||||
// Try to use it (it might be disabled, e.g. user is in private mode)
|
|
||||||
// see: https://github.com/sampotts/plyr/issues/131
|
|
||||||
window.localStorage.setItem(test, test);
|
|
||||||
window.localStorage.removeItem(test);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
const json = JSON.parse(store);
|
||||||
if (!Storage.supported || !this.enabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = window.localStorage.getItem(this.key);
|
return is.string(key) && key.length ? json[key] : json;
|
||||||
|
}
|
||||||
|
|
||||||
if (is.empty(store)) {
|
set(object) {
|
||||||
return null;
|
// Bail if we don't have localStorage support or it's disabled
|
||||||
}
|
if (!Storage.supported || !this.enabled) {
|
||||||
|
return;
|
||||||
const json = JSON.parse(store);
|
|
||||||
|
|
||||||
return is.string(key) && key.length ? json[key] : json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(object) {
|
// Can only store objectst
|
||||||
// Bail if we don't have localStorage support or it's disabled
|
if (!is.object(object)) {
|
||||||
if (!Storage.supported || !this.enabled) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can only store objectst
|
|
||||||
if (!is.object(object)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current storage
|
|
||||||
let storage = this.get();
|
|
||||||
|
|
||||||
// Default to empty object
|
|
||||||
if (is.empty(storage)) {
|
|
||||||
storage = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the working copy of the values
|
|
||||||
extend(storage, object);
|
|
||||||
|
|
||||||
// Update storage
|
|
||||||
window.localStorage.setItem(this.key, JSON.stringify(storage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current storage
|
||||||
|
let storage = this.get();
|
||||||
|
|
||||||
|
// Default to empty object
|
||||||
|
if (is.empty(storage)) {
|
||||||
|
storage = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the working copy of the values
|
||||||
|
extend(storage, object);
|
||||||
|
|
||||||
|
// Update storage
|
||||||
|
window.localStorage.setItem(this.key, JSON.stringify(storage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Storage;
|
export default Storage;
|
||||||
|
|||||||
+82
-82
@@ -9,110 +9,110 @@ import is from './utils/is';
|
|||||||
|
|
||||||
// Default codecs for checking mimetype support
|
// Default codecs for checking mimetype support
|
||||||
const defaultCodecs = {
|
const defaultCodecs = {
|
||||||
'audio/ogg': 'vorbis',
|
'audio/ogg': 'vorbis',
|
||||||
'audio/wav': '1',
|
'audio/wav': '1',
|
||||||
'video/webm': 'vp8, vorbis',
|
'video/webm': 'vp8, vorbis',
|
||||||
'video/mp4': 'avc1.42E01E, mp4a.40.2',
|
'video/mp4': 'avc1.42E01E, mp4a.40.2',
|
||||||
'video/ogg': 'theora',
|
'video/ogg': 'theora',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for feature support
|
// Check for feature support
|
||||||
const support = {
|
const support = {
|
||||||
// Basic support
|
// Basic support
|
||||||
audio: 'canPlayType' in document.createElement('audio'),
|
audio: 'canPlayType' in document.createElement('audio'),
|
||||||
video: 'canPlayType' in document.createElement('video'),
|
video: 'canPlayType' in document.createElement('video'),
|
||||||
|
|
||||||
// Check for support
|
// Check for support
|
||||||
// Basic functionality vs full UI
|
// Basic functionality vs full UI
|
||||||
check(type, provider, playsinline) {
|
check(type, provider, playsinline) {
|
||||||
const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
|
const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
|
||||||
const api = support[type] || provider !== 'html5';
|
const api = support[type] || provider !== 'html5';
|
||||||
const ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
|
const ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
ui,
|
ui,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// Picture-in-picture support
|
// Picture-in-picture support
|
||||||
// Safari & Chrome only currently
|
// Safari & Chrome only currently
|
||||||
pip: (() => {
|
pip: (() => {
|
||||||
if (browser.isIPhone) {
|
if (browser.isIPhone) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safari
|
// Safari
|
||||||
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
||||||
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chrome
|
// Chrome
|
||||||
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
||||||
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Airplay support
|
// Airplay support
|
||||||
// Safari only currently
|
// Safari only currently
|
||||||
airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent),
|
airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent),
|
||||||
|
|
||||||
// Inline playback support
|
// Inline playback support
|
||||||
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||||
playsinline: 'playsInline' in document.createElement('video'),
|
playsinline: 'playsInline' in document.createElement('video'),
|
||||||
|
|
||||||
// Check for mime type support against a player instance
|
// Check for mime type support against a player instance
|
||||||
// Credits: http://diveintohtml5.info/everything.html
|
// Credits: http://diveintohtml5.info/everything.html
|
||||||
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
||||||
mime(input) {
|
mime(input) {
|
||||||
if (is.empty(input)) {
|
if (is.empty(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [mediaType] = input.split('/');
|
const [mediaType] = input.split('/');
|
||||||
let type = input;
|
let type = input;
|
||||||
|
|
||||||
// Verify we're using HTML5 and there's no media type mismatch
|
// Verify we're using HTML5 and there's no media type mismatch
|
||||||
if (!this.isHTML5 || mediaType !== this.type) {
|
if (!this.isHTML5 || mediaType !== this.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add codec if required
|
// Add codec if required
|
||||||
if (Object.keys(defaultCodecs).includes(type)) {
|
if (Object.keys(defaultCodecs).includes(type)) {
|
||||||
type += `; codecs="${defaultCodecs[input]}"`;
|
type += `; codecs="${defaultCodecs[input]}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Check for textTracks support
|
// Check for textTracks support
|
||||||
textTracks: 'textTracks' in document.createElement('video'),
|
textTracks: 'textTracks' in document.createElement('video'),
|
||||||
|
|
||||||
// <input type="range"> Sliders
|
// <input type="range"> Sliders
|
||||||
rangeInput: (() => {
|
rangeInput: (() => {
|
||||||
const range = document.createElement('input');
|
const range = document.createElement('input');
|
||||||
range.type = 'range';
|
range.type = 'range';
|
||||||
return range.type === 'range';
|
return range.type === 'range';
|
||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
|
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
|
||||||
touch: 'ontouchstart' in document.documentElement,
|
touch: 'ontouchstart' in document.documentElement,
|
||||||
|
|
||||||
// Detect transitions support
|
// Detect transitions support
|
||||||
transitions: transitionEndEvent !== false,
|
transitions: transitionEndEvent !== false,
|
||||||
|
|
||||||
// Reduced motion iOS & MacOS setting
|
// Reduced motion iOS & MacOS setting
|
||||||
// https://webkit.org/blog/7551/responsive-design-for-motion/
|
// https://webkit.org/blog/7551/responsive-design-for-motion/
|
||||||
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches,
|
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default support;
|
export default support;
|
||||||
|
|||||||
+208
-213
@@ -13,267 +13,262 @@ import is from './utils/is';
|
|||||||
import loadImage from './utils/load-image';
|
import loadImage from './utils/load-image';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
||||||
toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
|
toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle native HTML5 media controls
|
// Toggle native HTML5 media controls
|
||||||
toggleNativeControls(toggle = false) {
|
toggleNativeControls(toggle = false) {
|
||||||
if (toggle && this.isHTML5) {
|
if (toggle && this.isHTML5) {
|
||||||
this.media.setAttribute('controls', '');
|
this.media.setAttribute('controls', '');
|
||||||
} else {
|
} else {
|
||||||
this.media.removeAttribute('controls');
|
this.media.removeAttribute('controls');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup the UI
|
// Setup the UI
|
||||||
build() {
|
build() {
|
||||||
// Re-attach media element listeners
|
// Re-attach media element listeners
|
||||||
// TODO: Use event bubbling?
|
// TODO: Use event bubbling?
|
||||||
this.listeners.media();
|
this.listeners.media();
|
||||||
|
|
||||||
// Don't setup interface if no support
|
// Don't setup interface if no support
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui) {
|
||||||
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
|
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
|
||||||
|
|
||||||
// Restore native controls
|
// Restore native controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
// Bail
|
// Bail
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject custom controls if not present
|
// Inject custom controls if not present
|
||||||
if (!is.element(this.elements.controls)) {
|
if (!is.element(this.elements.controls)) {
|
||||||
// Inject custom controls
|
// Inject custom controls
|
||||||
controls.inject.call(this);
|
controls.inject.call(this);
|
||||||
|
|
||||||
// Re-attach control listeners
|
// Re-attach control listeners
|
||||||
this.listeners.controls();
|
this.listeners.controls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Setup captions for HTML5
|
// Setup captions for HTML5
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
captions.setup.call(this);
|
captions.setup.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset volume
|
// Reset volume
|
||||||
this.volume = null;
|
this.volume = null;
|
||||||
|
|
||||||
// Reset mute state
|
// Reset mute state
|
||||||
this.muted = null;
|
this.muted = null;
|
||||||
|
|
||||||
// Reset loop state
|
// Reset loop state
|
||||||
this.loop = null;
|
this.loop = null;
|
||||||
|
|
||||||
// Reset quality setting
|
// Reset quality setting
|
||||||
this.quality = null;
|
this.quality = null;
|
||||||
|
|
||||||
// Reset speed
|
// Reset speed
|
||||||
this.speed = null;
|
this.speed = null;
|
||||||
|
|
||||||
// Reset volume display
|
// Reset volume display
|
||||||
controls.updateVolume.call(this);
|
controls.updateVolume.call(this);
|
||||||
|
|
||||||
// Reset time display
|
// Reset time display
|
||||||
controls.timeUpdate.call(this);
|
controls.timeUpdate.call(this);
|
||||||
|
|
||||||
// Update the UI
|
// Update the UI
|
||||||
ui.checkPlaying.call(this);
|
ui.checkPlaying.call(this);
|
||||||
|
|
||||||
// Check for picture-in-picture support
|
// Check for picture-in-picture support
|
||||||
toggleClass(
|
toggleClass(
|
||||||
this.elements.container,
|
this.elements.container,
|
||||||
this.config.classNames.pip.supported,
|
this.config.classNames.pip.supported,
|
||||||
support.pip && this.isHTML5 && this.isVideo,
|
support.pip && this.isHTML5 && this.isVideo,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check for airplay support
|
// Check for airplay support
|
||||||
toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
|
toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
|
||||||
|
|
||||||
// Add iOS class
|
// Add iOS class
|
||||||
toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||||
|
|
||||||
// Add touch class
|
// Add touch class
|
||||||
toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||||
|
|
||||||
// Ready for API calls
|
// Ready for API calls
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
|
|
||||||
// Ready event at end of execution stack
|
// Ready event at end of execution stack
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
triggerEvent.call(this, this.media, 'ready');
|
triggerEvent.call(this, this.media, 'ready');
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Set the title
|
// Set the title
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
|
|
||||||
// Assure the poster image is set, if the property was added before the element was created
|
// Assure the poster image is set, if the property was added before the element was created
|
||||||
if (this.poster) {
|
if (this.poster) {
|
||||||
ui.setPoster.call(this, this.poster, false).catch(() => {});
|
ui.setPoster.call(this, this.poster, false).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually set the duration if user has overridden it.
|
// Manually set the duration if user has overridden it.
|
||||||
// The event listeners for it doesn't get called if preload is disabled (#701)
|
// The event listeners for it doesn't get called if preload is disabled (#701)
|
||||||
if (this.config.duration) {
|
if (this.config.duration) {
|
||||||
controls.durationUpdate.call(this);
|
controls.durationUpdate.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup aria attribute for play and iframe title
|
// Setup aria attribute for play and iframe title
|
||||||
setTitle() {
|
setTitle() {
|
||||||
// Find the current text
|
// Find the current text
|
||||||
let label = i18n.get('play', this.config);
|
let label = i18n.get('play', this.config);
|
||||||
|
|
||||||
// If there's a media title set, use that for the label
|
// If there's a media title set, use that for the label
|
||||||
if (is.string(this.config.title) && !is.empty(this.config.title)) {
|
if (is.string(this.config.title) && !is.empty(this.config.title)) {
|
||||||
label += `, ${this.config.title}`;
|
label += `, ${this.config.title}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a play button, set label
|
// If there's a play button, set label
|
||||||
Array.from(this.elements.buttons.play || []).forEach(button => {
|
Array.from(this.elements.buttons.play || []).forEach(button => {
|
||||||
button.setAttribute('aria-label', label);
|
button.setAttribute('aria-label', label);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set iframe title
|
// Set iframe title
|
||||||
// https://github.com/sampotts/plyr/issues/124
|
// https://github.com/sampotts/plyr/issues/124
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
const iframe = getElement.call(this, 'iframe');
|
const iframe = getElement.call(this, 'iframe');
|
||||||
|
|
||||||
if (!is.element(iframe)) {
|
if (!is.element(iframe)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to media type
|
// Default to media type
|
||||||
const title = !is.empty(this.config.title) ? this.config.title : 'video';
|
const title = !is.empty(this.config.title) ? this.config.title : 'video';
|
||||||
const format = i18n.get('frameTitle', this.config);
|
const format = i18n.get('frameTitle', this.config);
|
||||||
|
|
||||||
iframe.setAttribute('title', format.replace('{title}', title));
|
iframe.setAttribute('title', format.replace('{title}', title));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle poster
|
// Toggle poster
|
||||||
togglePoster(enable) {
|
togglePoster(enable) {
|
||||||
toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
|
toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the poster image (async)
|
// Set the poster image (async)
|
||||||
// Used internally for the poster setter, with the passive option forced to false
|
// Used internally for the poster setter, with the passive option forced to false
|
||||||
setPoster(poster, passive = true) {
|
setPoster(poster, passive = true) {
|
||||||
// Don't override if call is passive
|
// Don't override if call is passive
|
||||||
if (passive && this.poster) {
|
if (passive && this.poster) {
|
||||||
return Promise.reject(new Error('Poster already set'));
|
return Promise.reject(new Error('Poster already set'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set property synchronously to respect the call order
|
// Set property synchronously to respect the call order
|
||||||
this.media.setAttribute('poster', poster);
|
this.media.setAttribute('poster', poster);
|
||||||
|
|
||||||
// HTML5 uses native poster attribute
|
// HTML5 uses native poster attribute
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
return Promise.resolve(poster);
|
return Promise.resolve(poster);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until ui is ready
|
// Wait until ui is ready
|
||||||
return (
|
return (
|
||||||
ready
|
ready
|
||||||
.call(this)
|
.call(this)
|
||||||
// Load image
|
// Load image
|
||||||
.then(() => loadImage(poster))
|
.then(() => loadImage(poster))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// Hide poster on error unless it's been set by another call
|
// Hide poster on error unless it's been set by another call
|
||||||
if (poster === this.poster) {
|
if (poster === this.poster) {
|
||||||
ui.togglePoster.call(this, false);
|
ui.togglePoster.call(this, false);
|
||||||
}
|
}
|
||||||
// Rethrow
|
// Rethrow
|
||||||
throw err;
|
throw err;
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Prevent race conditions
|
// Prevent race conditions
|
||||||
if (poster !== this.poster) {
|
if (poster !== this.poster) {
|
||||||
throw new Error('setPoster cancelled by later call to setPoster');
|
throw new Error('setPoster cancelled by later call to setPoster');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Object.assign(this.elements.poster.style, {
|
Object.assign(this.elements.poster.style, {
|
||||||
backgroundImage: `url('${poster}')`,
|
backgroundImage: `url('${poster}')`,
|
||||||
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
||||||
backgroundSize: '',
|
backgroundSize: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.togglePoster.call(this, true);
|
ui.togglePoster.call(this, true);
|
||||||
|
|
||||||
return poster;
|
return poster;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Check playing state
|
// Check playing state
|
||||||
checkPlaying(event) {
|
checkPlaying(event) {
|
||||||
// Class hooks
|
// Class hooks
|
||||||
toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||||
toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
||||||
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
||||||
|
|
||||||
// Set state
|
// Set state
|
||||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||||
Object.assign(target, { pressed: this.playing });
|
Object.assign(target, { pressed: this.playing });
|
||||||
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only update controls on non timeupdate events
|
// Only update controls on non timeupdate events
|
||||||
if (is.event(event) && event.type === 'timeupdate') {
|
if (is.event(event) && event.type === 'timeupdate') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle controls
|
// Toggle controls
|
||||||
|
ui.toggleControls.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check if media is loading
|
||||||
|
checkLoading(event) {
|
||||||
|
this.loading = ['stalled', 'waiting'].includes(event.type);
|
||||||
|
|
||||||
|
// Clear timer
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
|
// Timer to prevent flicker when seeking
|
||||||
|
this.timers.loading = setTimeout(
|
||||||
|
() => {
|
||||||
|
// Update progress bar loading class state
|
||||||
|
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
||||||
|
|
||||||
|
// Update controls visibility
|
||||||
ui.toggleControls.call(this);
|
ui.toggleControls.call(this);
|
||||||
},
|
},
|
||||||
|
this.loading ? 250 : 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// Check if media is loading
|
// Toggle controls based on state and `force` argument
|
||||||
checkLoading(event) {
|
toggleControls(force) {
|
||||||
this.loading = ['stalled', 'waiting'].includes(event.type);
|
const { controls: controlsElement } = this.elements;
|
||||||
|
|
||||||
// Clear timer
|
if (controlsElement && this.config.hideControls) {
|
||||||
clearTimeout(this.timers.loading);
|
// 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();
|
||||||
|
|
||||||
// Timer to prevent flicker when seeking
|
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
||||||
this.timers.loading = setTimeout(
|
this.toggleControls(
|
||||||
() => {
|
Boolean(
|
||||||
// Update progress bar loading class state
|
force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek,
|
||||||
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
),
|
||||||
|
);
|
||||||
// Update controls visibility
|
}
|
||||||
ui.toggleControls.call(this);
|
},
|
||||||
},
|
|
||||||
this.loading ? 250 : 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Toggle controls based on state and `force` argument
|
|
||||||
toggleControls(force) {
|
|
||||||
const { controls: controlsElement } = this.elements;
|
|
||||||
|
|
||||||
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 ||
|
|
||||||
controlsElement.pressed ||
|
|
||||||
controlsElement.hover ||
|
|
||||||
recentTouchSeek,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ui;
|
export default ui;
|
||||||
|
|||||||
+21
-21
@@ -5,34 +5,34 @@
|
|||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
export const transitionEndEvent = (() => {
|
export const transitionEndEvent = (() => {
|
||||||
const element = document.createElement('span');
|
const element = document.createElement('span');
|
||||||
|
|
||||||
const events = {
|
const events = {
|
||||||
WebkitTransition: 'webkitTransitionEnd',
|
WebkitTransition: 'webkitTransitionEnd',
|
||||||
MozTransition: 'transitionend',
|
MozTransition: 'transitionend',
|
||||||
OTransition: 'oTransitionEnd otransitionend',
|
OTransition: 'oTransitionEnd otransitionend',
|
||||||
transition: 'transitionend',
|
transition: 'transitionend',
|
||||||
};
|
};
|
||||||
|
|
||||||
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
||||||
|
|
||||||
return is.string(type) ? events[type] : false;
|
return is.string(type) ? events[type] : false;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
export function repaint(element, delay) {
|
export function repaint(element, delay) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = true;
|
element.hidden = true;
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
element.offsetHeight;
|
element.offsetHeight;
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = false;
|
element.hidden = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ import is from './is';
|
|||||||
|
|
||||||
// Remove duplicates in an array
|
// Remove duplicates in an array
|
||||||
export function dedupe(array) {
|
export function dedupe(array) {
|
||||||
if (!is.array(array)) {
|
if (!is.array(array)) {
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array.filter((item, index) => array.indexOf(item) === index);
|
return array.filter((item, index) => array.indexOf(item) === index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the closest value in an array
|
// Get the closest value in an array
|
||||||
export function closest(array, value) {
|
export function closest(array, value) {
|
||||||
if (!is.array(array) || !array.length) {
|
if (!is.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const browser = {
|
const browser = {
|
||||||
isIE: /* @cc_on!@ */ false || !!document.documentMode,
|
isIE: /* @cc_on!@ */ false || !!document.documentMode,
|
||||||
isEdge: window.navigator.userAgent.includes('Edge'),
|
isEdge: window.navigator.userAgent.includes('Edge'),
|
||||||
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||||
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||||
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default browser;
|
export default browser;
|
||||||
|
|||||||
+161
-161
@@ -7,257 +7,257 @@ import { extend } from './objects';
|
|||||||
|
|
||||||
// Wrap an element
|
// Wrap an element
|
||||||
export function wrap(elements, wrapper) {
|
export function wrap(elements, wrapper) {
|
||||||
// Convert `elements` to an array, if necessary.
|
// Convert `elements` to an array, if necessary.
|
||||||
const targets = elements.length ? elements : [elements];
|
const targets = elements.length ? elements : [elements];
|
||||||
|
|
||||||
// Loops backwards to prevent having to clone the wrapper on the
|
// Loops backwards to prevent having to clone the wrapper on the
|
||||||
// first element (see `child` below).
|
// first element (see `child` below).
|
||||||
Array.from(targets)
|
Array.from(targets)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach((element, index) => {
|
.forEach((element, index) => {
|
||||||
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
||||||
// Cache the current parent and sibling.
|
// Cache the current parent and sibling.
|
||||||
const parent = element.parentNode;
|
const parent = element.parentNode;
|
||||||
const sibling = element.nextSibling;
|
const sibling = element.nextSibling;
|
||||||
|
|
||||||
// Wrap the element (is automatically removed from its current
|
// Wrap the element (is automatically removed from its current
|
||||||
// parent).
|
// parent).
|
||||||
child.appendChild(element);
|
child.appendChild(element);
|
||||||
|
|
||||||
// If the element had a sibling, insert the wrapper before
|
// If the element had a sibling, insert the wrapper before
|
||||||
// the sibling to maintain the HTML structure; otherwise, just
|
// the sibling to maintain the HTML structure; otherwise, just
|
||||||
// append it to the parent.
|
// append it to the parent.
|
||||||
if (sibling) {
|
if (sibling) {
|
||||||
parent.insertBefore(child, sibling);
|
parent.insertBefore(child, sibling);
|
||||||
} else {
|
} else {
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set attributes
|
// Set attributes
|
||||||
export function setAttributes(element, attributes) {
|
export function setAttributes(element, attributes) {
|
||||||
if (!is.element(element) || is.empty(attributes)) {
|
if (!is.element(element) || is.empty(attributes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume null and undefined attributes should be left out,
|
// Assume null and undefined attributes should be left out,
|
||||||
// Setting them would otherwise convert them to "null" and "undefined"
|
// Setting them would otherwise convert them to "null" and "undefined"
|
||||||
Object.entries(attributes)
|
Object.entries(attributes)
|
||||||
.filter(([, value]) => !is.nullOrUndefined(value))
|
.filter(([, value]) => !is.nullOrUndefined(value))
|
||||||
.forEach(([key, value]) => element.setAttribute(key, value));
|
.forEach(([key, value]) => element.setAttribute(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a DocumentFragment
|
// Create a DocumentFragment
|
||||||
export function createElement(type, attributes, text) {
|
export function createElement(type, attributes, text) {
|
||||||
// Create a new <element>
|
// Create a new <element>
|
||||||
const element = document.createElement(type);
|
const element = document.createElement(type);
|
||||||
|
|
||||||
// Set all passed attributes
|
// Set all passed attributes
|
||||||
if (is.object(attributes)) {
|
if (is.object(attributes)) {
|
||||||
setAttributes(element, attributes);
|
setAttributes(element, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add text node
|
// Add text node
|
||||||
if (is.string(text)) {
|
if (is.string(text)) {
|
||||||
element.innerText = text;
|
element.innerText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return built element
|
// Return built element
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inaert an element after another
|
// Inaert an element after another
|
||||||
export function insertAfter(element, target) {
|
export function insertAfter(element, target) {
|
||||||
if (!is.element(element) || !is.element(target)) {
|
if (!is.element(element) || !is.element(target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
target.parentNode.insertBefore(element, target.nextSibling);
|
target.parentNode.insertBefore(element, target.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a DocumentFragment
|
// Insert a DocumentFragment
|
||||||
export function insertElement(type, parent, attributes, text) {
|
export function insertElement(type, parent, attributes, text) {
|
||||||
if (!is.element(parent)) {
|
if (!is.element(parent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.appendChild(createElement(type, attributes, text));
|
parent.appendChild(createElement(type, attributes, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove element(s)
|
// Remove element(s)
|
||||||
export function removeElement(element) {
|
export function removeElement(element) {
|
||||||
if (is.nodeList(element) || is.array(element)) {
|
if (is.nodeList(element) || is.array(element)) {
|
||||||
Array.from(element).forEach(removeElement);
|
Array.from(element).forEach(removeElement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is.element(element) || !is.element(element.parentNode)) {
|
if (!is.element(element) || !is.element(element.parentNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.parentNode.removeChild(element);
|
element.parentNode.removeChild(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
export function emptyElement(element) {
|
export function emptyElement(element) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { length } = element.childNodes;
|
let { length } = element.childNodes;
|
||||||
|
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
element.removeChild(element.lastChild);
|
element.removeChild(element.lastChild);
|
||||||
length -= 1;
|
length -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace element
|
// Replace element
|
||||||
export function replaceElement(newChild, oldChild) {
|
export function replaceElement(newChild, oldChild) {
|
||||||
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
|
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldChild.parentNode.replaceChild(newChild, oldChild);
|
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||||||
|
|
||||||
return newChild;
|
return newChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an attribute object from a string selector
|
// Get an attribute object from a string selector
|
||||||
export function getAttributesFromSelector(sel, existingAttributes) {
|
export function getAttributesFromSelector(sel, existingAttributes) {
|
||||||
// For example:
|
// For example:
|
||||||
// '.test' to { class: 'test' }
|
// '.test' to { class: 'test' }
|
||||||
// '#test' to { id: 'test' }
|
// '#test' to { id: 'test' }
|
||||||
// '[data-test="test"]' to { 'data-test': 'test' }
|
// '[data-test="test"]' to { 'data-test': 'test' }
|
||||||
|
|
||||||
if (!is.string(sel) || is.empty(sel)) {
|
if (!is.string(sel) || is.empty(sel)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
const existing = extend({}, existingAttributes);
|
const existing = extend({}, existingAttributes);
|
||||||
|
|
||||||
sel.split(',').forEach(s => {
|
sel.split(',').forEach(s => {
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
const selector = s.trim();
|
const selector = s.trim();
|
||||||
const className = selector.replace('.', '');
|
const className = selector.replace('.', '');
|
||||||
const stripped = selector.replace(/[[\]]/g, '');
|
const stripped = selector.replace(/[[\]]/g, '');
|
||||||
// Get the parts and value
|
// Get the parts and value
|
||||||
const parts = stripped.split('=');
|
const parts = stripped.split('=');
|
||||||
const [key] = parts;
|
const [key] = parts;
|
||||||
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
||||||
// Get the first character
|
// Get the first character
|
||||||
const start = selector.charAt(0);
|
const start = selector.charAt(0);
|
||||||
|
|
||||||
switch (start) {
|
switch (start) {
|
||||||
case '.':
|
case '.':
|
||||||
// Add to existing classname
|
// Add to existing classname
|
||||||
if (is.string(existing.class)) {
|
if (is.string(existing.class)) {
|
||||||
attributes.class = `${existing.class} ${className}`;
|
attributes.class = `${existing.class} ${className}`;
|
||||||
} else {
|
} else {
|
||||||
attributes.class = className;
|
attributes.class = className;
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '#':
|
|
||||||
// ID selector
|
|
||||||
attributes.id = selector.replace('#', '');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '[':
|
|
||||||
// Attribute selector
|
|
||||||
attributes[key] = value;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
break;
|
||||||
|
|
||||||
return extend(existing, attributes);
|
case '#':
|
||||||
|
// ID selector
|
||||||
|
attributes.id = selector.replace('#', '');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
// Attribute selector
|
||||||
|
attributes[key] = value;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return extend(existing, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle hidden
|
// Toggle hidden
|
||||||
export function toggleHidden(element, hidden) {
|
export function toggleHidden(element, hidden) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hide = hidden;
|
let hide = hidden;
|
||||||
|
|
||||||
if (!is.boolean(hide)) {
|
if (!is.boolean(hide)) {
|
||||||
hide = !element.hidden;
|
hide = !element.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = hide;
|
element.hidden = hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||||
export function toggleClass(element, className, force) {
|
export function toggleClass(element, className, force) {
|
||||||
if (is.nodeList(element)) {
|
if (is.nodeList(element)) {
|
||||||
return Array.from(element).map(e => toggleClass(e, className, force));
|
return Array.from(element).map(e => toggleClass(e, className, force));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.element(element)) {
|
||||||
|
let method = 'toggle';
|
||||||
|
if (typeof force !== 'undefined') {
|
||||||
|
method = force ? 'add' : 'remove';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is.element(element)) {
|
element.classList[method](className);
|
||||||
let method = 'toggle';
|
return element.classList.contains(className);
|
||||||
if (typeof force !== 'undefined') {
|
}
|
||||||
method = force ? 'add' : 'remove';
|
|
||||||
}
|
|
||||||
|
|
||||||
element.classList[method](className);
|
return false;
|
||||||
return element.classList.contains(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has class name
|
// Has class name
|
||||||
export function hasClass(element, className) {
|
export function hasClass(element, className) {
|
||||||
return is.element(element) && element.classList.contains(className);
|
return is.element(element) && element.classList.contains(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element matches selector
|
// Element matches selector
|
||||||
export function matches(element, selector) {
|
export function matches(element, selector) {
|
||||||
const { prototype } = Element;
|
const { prototype } = Element;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const method =
|
const method =
|
||||||
prototype.matches ||
|
prototype.matches ||
|
||||||
prototype.webkitMatchesSelector ||
|
prototype.webkitMatchesSelector ||
|
||||||
prototype.mozMatchesSelector ||
|
prototype.mozMatchesSelector ||
|
||||||
prototype.msMatchesSelector ||
|
prototype.msMatchesSelector ||
|
||||||
match;
|
match;
|
||||||
|
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all elements
|
// Find all elements
|
||||||
export function getElements(selector) {
|
export function getElements(selector) {
|
||||||
return this.elements.container.querySelectorAll(selector);
|
return this.elements.container.querySelectorAll(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a single element
|
// Find a single element
|
||||||
export function getElement(selector) {
|
export function getElement(selector) {
|
||||||
return this.elements.container.querySelector(selector);
|
return this.elements.container.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set focus and tab focus class
|
// Set focus and tab focus class
|
||||||
export function setFocus(element = null, tabFocus = false) {
|
export function setFocus(element = null, tabFocus = false) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set regular focus
|
// Set regular focus
|
||||||
element.focus({ preventScroll: true });
|
element.focus({ preventScroll: true });
|
||||||
|
|
||||||
// If we want to mimic keyboard focus via tab
|
// If we want to mimic keyboard focus via tab
|
||||||
if (tabFocus) {
|
if (tabFocus) {
|
||||||
toggleClass(element, this.config.classNames.tabFocus);
|
toggleClass(element, this.config.classNames.tabFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-71
@@ -8,110 +8,110 @@ import is from './is';
|
|||||||
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
||||||
// https://www.youtube.com/watch?v=NPM6172J22g
|
// https://www.youtube.com/watch?v=NPM6172J22g
|
||||||
const supportsPassiveListeners = (() => {
|
const supportsPassiveListeners = (() => {
|
||||||
// Test via a getter in the options object to see if the passive property is accessed
|
// Test via a getter in the options object to see if the passive property is accessed
|
||||||
let supported = false;
|
let supported = false;
|
||||||
try {
|
try {
|
||||||
const options = Object.defineProperty({}, 'passive', {
|
const options = Object.defineProperty({}, 'passive', {
|
||||||
get() {
|
get() {
|
||||||
supported = true;
|
supported = true;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
window.addEventListener('test', null, options);
|
window.addEventListener('test', null, options);
|
||||||
window.removeEventListener('test', null, options);
|
window.removeEventListener('test', null, options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
return supported;
|
return supported;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Toggle event listener
|
// Toggle event listener
|
||||||
export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
|
export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
|
||||||
// Bail if no element, event, or callback
|
// Bail if no element, event, or callback
|
||||||
if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
|
if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow multiple events
|
||||||
|
const events = event.split(' ');
|
||||||
|
// Build options
|
||||||
|
// Default to just the capture boolean for browsers with no passive listener support
|
||||||
|
let options = capture;
|
||||||
|
|
||||||
|
// If passive events listeners are supported
|
||||||
|
if (supportsPassiveListeners) {
|
||||||
|
options = {
|
||||||
|
// Whether the listener can be passive (i.e. default never prevented)
|
||||||
|
passive,
|
||||||
|
// Whether the listener is a capturing listener or not
|
||||||
|
capture,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a single node is passed, bind the event listener
|
||||||
|
events.forEach(type => {
|
||||||
|
if (this && this.eventListeners && toggle) {
|
||||||
|
// Cache event listener
|
||||||
|
this.eventListeners.push({ element, type, callback, options });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow multiple events
|
element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
|
||||||
const events = event.split(' ');
|
});
|
||||||
// Build options
|
|
||||||
// Default to just the capture boolean for browsers with no passive listener support
|
|
||||||
let options = capture;
|
|
||||||
|
|
||||||
// If passive events listeners are supported
|
|
||||||
if (supportsPassiveListeners) {
|
|
||||||
options = {
|
|
||||||
// Whether the listener can be passive (i.e. default never prevented)
|
|
||||||
passive,
|
|
||||||
// Whether the listener is a capturing listener or not
|
|
||||||
capture,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a single node is passed, bind the event listener
|
|
||||||
events.forEach(type => {
|
|
||||||
if (this && this.eventListeners && toggle) {
|
|
||||||
// Cache event listener
|
|
||||||
this.eventListeners.push({ element, type, callback, options });
|
|
||||||
}
|
|
||||||
|
|
||||||
element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind event handler
|
// Bind event handler
|
||||||
export function on(element, events = '', callback, passive = true, capture = false) {
|
export function on(element, events = '', callback, passive = true, capture = false) {
|
||||||
toggleListener.call(this, element, events, callback, true, passive, capture);
|
toggleListener.call(this, element, events, callback, true, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind event handler
|
// Unbind event handler
|
||||||
export function off(element, events = '', callback, passive = true, capture = false) {
|
export function off(element, events = '', callback, passive = true, capture = false) {
|
||||||
toggleListener.call(this, element, events, callback, false, passive, capture);
|
toggleListener.call(this, element, events, callback, false, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind once-only event handler
|
// Bind once-only event handler
|
||||||
export function once(element, events = '', callback, passive = true, capture = false) {
|
export function once(element, events = '', callback, passive = true, capture = false) {
|
||||||
const onceCallback = (...args) => {
|
const onceCallback = (...args) => {
|
||||||
off(element, events, onceCallback, passive, capture);
|
off(element, events, onceCallback, passive, capture);
|
||||||
callback.apply(this, args);
|
callback.apply(this, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
|
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
||||||
// Bail if no element
|
// Bail if no element
|
||||||
if (!is.element(element) || is.empty(type)) {
|
if (!is.element(element) || is.empty(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and dispatch the event
|
// Create and dispatch the event
|
||||||
const event = new CustomEvent(type, {
|
const event = new CustomEvent(type, {
|
||||||
bubbles,
|
bubbles,
|
||||||
detail: { ...detail, plyr: this },
|
detail: { ...detail, plyr: this },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dispatch the event
|
// Dispatch the event
|
||||||
element.dispatchEvent(event);
|
element.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind all cached event listeners
|
// Unbind all cached event listeners
|
||||||
export function unbindListeners() {
|
export function unbindListeners() {
|
||||||
if (this && this.eventListeners) {
|
if (this && this.eventListeners) {
|
||||||
this.eventListeners.forEach(item => {
|
this.eventListeners.forEach(item => {
|
||||||
const { element, type, callback, options } = item;
|
const { element, type, callback, options } = item;
|
||||||
element.removeEventListener(type, callback, options);
|
element.removeEventListener(type, callback, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventListeners = [];
|
this.eventListeners = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run method when / if player is ready
|
// Run method when / if player is ready
|
||||||
export function ready() {
|
export function ready() {
|
||||||
return new Promise(resolve =>
|
return new Promise(resolve =>
|
||||||
this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve),
|
this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve),
|
||||||
).then(() => {});
|
).then(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-32
@@ -4,39 +4,39 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export default function fetch(url, responseType = 'text') {
|
export default function fetch(url, responseType = 'text') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
|
|
||||||
// Check for CORS support
|
// Check for CORS support
|
||||||
if (!('withCredentials' in request)) {
|
if (!('withCredentials' in request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addEventListener('load', () => {
|
request.addEventListener('load', () => {
|
||||||
if (responseType === 'text') {
|
if (responseType === 'text') {
|
||||||
try {
|
try {
|
||||||
resolve(JSON.parse(request.responseText));
|
resolve(JSON.parse(request.responseText));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resolve(request.responseText);
|
resolve(request.responseText);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(request.response);
|
resolve(request.response);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
request.addEventListener('error', () => {
|
|
||||||
throw new Error(request.status);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.open('GET', url, true);
|
|
||||||
|
|
||||||
// Set the required response type
|
|
||||||
request.responseType = responseType;
|
|
||||||
|
|
||||||
request.send();
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
request.addEventListener('error', () => {
|
||||||
|
throw new Error(request.status);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.open('GET', url, true);
|
||||||
|
|
||||||
|
// Set the required response type
|
||||||
|
request.responseType = responseType;
|
||||||
|
|
||||||
|
request.send();
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -8,40 +8,40 @@ import { replaceAll } from './strings';
|
|||||||
|
|
||||||
// Skip i18n for abbreviations and brand names
|
// Skip i18n for abbreviations and brand names
|
||||||
const resources = {
|
const resources = {
|
||||||
pip: 'PIP',
|
pip: 'PIP',
|
||||||
airplay: 'AirPlay',
|
airplay: 'AirPlay',
|
||||||
html5: 'HTML5',
|
html5: 'HTML5',
|
||||||
vimeo: 'Vimeo',
|
vimeo: 'Vimeo',
|
||||||
youtube: 'YouTube',
|
youtube: 'YouTube',
|
||||||
};
|
};
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
get(key = '', config = {}) {
|
get(key = '', config = {}) {
|
||||||
if (is.empty(key) || is.empty(config)) {
|
if (is.empty(key) || is.empty(config)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = getDeep(config.i18n, key);
|
let string = getDeep(config.i18n, key);
|
||||||
|
|
||||||
if (is.empty(string)) {
|
if (is.empty(string)) {
|
||||||
if (Object.keys(resources).includes(key)) {
|
if (Object.keys(resources).includes(key)) {
|
||||||
return resources[key];
|
return resources[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const replace = {
|
const replace = {
|
||||||
'{seektime}': config.seekTime,
|
'{seektime}': config.seekTime,
|
||||||
'{title}': config.title,
|
'{title}': config.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(replace).forEach(([k, v]) => {
|
Object.entries(replace).forEach(([k, v]) => {
|
||||||
string = replaceAll(string, k, v);
|
string = replaceAll(string, k, v);
|
||||||
});
|
});
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
+39
-39
@@ -22,51 +22,51 @@ const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(inp
|
|||||||
const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
|
const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
|
||||||
|
|
||||||
const isEmpty = input =>
|
const isEmpty = input =>
|
||||||
isNullOrUndefined(input) ||
|
isNullOrUndefined(input) ||
|
||||||
((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
|
((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
|
||||||
(isObject(input) && !Object.keys(input).length);
|
(isObject(input) && !Object.keys(input).length);
|
||||||
|
|
||||||
const isUrl = input => {
|
const isUrl = input => {
|
||||||
// Accept a URL object
|
// Accept a URL object
|
||||||
if (instanceOf(input, window.URL)) {
|
if (instanceOf(input, window.URL)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be string from here
|
// Must be string from here
|
||||||
if (!isString(input)) {
|
if (!isString(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the protocol if required
|
// Add the protocol if required
|
||||||
let string = input;
|
let string = input;
|
||||||
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
||||||
string = `http://${input}`;
|
string = `http://${input}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return !isEmpty(new URL(string).hostname);
|
return !isEmpty(new URL(string).hostname);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
nullOrUndefined: isNullOrUndefined,
|
nullOrUndefined: isNullOrUndefined,
|
||||||
object: isObject,
|
object: isObject,
|
||||||
number: isNumber,
|
number: isNumber,
|
||||||
string: isString,
|
string: isString,
|
||||||
boolean: isBoolean,
|
boolean: isBoolean,
|
||||||
function: isFunction,
|
function: isFunction,
|
||||||
array: isArray,
|
array: isArray,
|
||||||
weakMap: isWeakMap,
|
weakMap: isWeakMap,
|
||||||
nodeList: isNodeList,
|
nodeList: isNodeList,
|
||||||
element: isElement,
|
element: isElement,
|
||||||
textNode: isTextNode,
|
textNode: isTextNode,
|
||||||
event: isEvent,
|
event: isEvent,
|
||||||
keyboardEvent: isKeyboardEvent,
|
keyboardEvent: isKeyboardEvent,
|
||||||
cue: isCue,
|
cue: isCue,
|
||||||
track: isTrack,
|
track: isTrack,
|
||||||
promise: isPromise,
|
promise: isPromise,
|
||||||
url: isUrl,
|
url: isUrl,
|
||||||
empty: isEmpty,
|
empty: isEmpty,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export default function loadImage(src, minWidth = 1) {
|
export default function loadImage(src, minWidth = 1) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
delete image.onload;
|
delete image.onload;
|
||||||
delete image.onerror;
|
delete image.onerror;
|
||||||
(image.naturalWidth >= minWidth ? resolve : reject)(image);
|
(image.naturalWidth >= minWidth ? resolve : reject)(image);
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(image, { onload: handler, onerror: handler, src });
|
Object.assign(image, { onload: handler, onerror: handler, src });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
import loadjs from 'loadjs';
|
import loadjs from 'loadjs';
|
||||||
|
|
||||||
export default function loadScript(url) {
|
export default function loadScript(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
loadjs(url, {
|
loadjs(url, {
|
||||||
success: resolve,
|
success: resolve,
|
||||||
error: reject,
|
error: reject,
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-55
@@ -8,68 +8,68 @@ import is from './is';
|
|||||||
|
|
||||||
// Load an external SVG sprite
|
// Load an external SVG sprite
|
||||||
export default function loadSprite(url, id) {
|
export default function loadSprite(url, id) {
|
||||||
if (!is.string(url)) {
|
if (!is.string(url)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = 'cache';
|
||||||
|
const hasId = is.string(id);
|
||||||
|
let isCached = false;
|
||||||
|
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
|
||||||
|
if (hasId && exists()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = 'cache';
|
// Inject the SVG to the body
|
||||||
const hasId = is.string(id);
|
document.body.insertAdjacentElement('afterbegin', container);
|
||||||
let isCached = false;
|
};
|
||||||
const exists = () => document.getElementById(id) !== null;
|
|
||||||
|
|
||||||
const update = (container, data) => {
|
// Only load once if ID set
|
||||||
// eslint-disable-next-line no-param-reassign
|
if (!hasId || !exists()) {
|
||||||
container.innerHTML = data;
|
const useStorage = Storage.supported;
|
||||||
|
// Create container
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.setAttribute('hidden', '');
|
||||||
|
|
||||||
// Check again incase of race condition
|
if (hasId) {
|
||||||
if (hasId && exists()) {
|
container.setAttribute('id', id);
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// Check in cache
|
||||||
|
if (useStorage) {
|
||||||
|
const cached = window.localStorage.getItem(`${prefix}-${id}`);
|
||||||
|
isCached = cached !== null;
|
||||||
|
|
||||||
|
if (isCached) {
|
||||||
|
const data = JSON.parse(cached);
|
||||||
|
update(container, data.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sprite
|
||||||
|
fetch(url)
|
||||||
|
.then(result => {
|
||||||
|
if (is.empty(result)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the SVG to the body
|
|
||||||
document.body.insertAdjacentElement('afterbegin', container);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only load once if ID set
|
|
||||||
if (!hasId || !exists()) {
|
|
||||||
const useStorage = Storage.supported;
|
|
||||||
// Create container
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.setAttribute('hidden', '');
|
|
||||||
|
|
||||||
if (hasId) {
|
|
||||||
container.setAttribute('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check in cache
|
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
const cached = window.localStorage.getItem(`${prefix}-${id}`);
|
window.localStorage.setItem(
|
||||||
isCached = cached !== null;
|
`${prefix}-${id}`,
|
||||||
|
JSON.stringify({
|
||||||
if (isCached) {
|
content: result,
|
||||||
const data = JSON.parse(cached);
|
}),
|
||||||
update(container, data.content);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the sprite
|
update(container, result);
|
||||||
fetch(url)
|
})
|
||||||
.then(result => {
|
.catch(() => {});
|
||||||
if (is.empty(result)) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useStorage) {
|
|
||||||
window.localStorage.setItem(
|
|
||||||
`${prefix}-${id}`,
|
|
||||||
JSON.stringify({
|
|
||||||
content: result,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(container, result);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
export function clamp(input = 0, min = 0, max = 255) {
|
export function clamp(input = 0, min = 0, max = 255) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { clamp };
|
export default { clamp };
|
||||||
|
|||||||
+23
-23
@@ -6,37 +6,37 @@ import is from './is';
|
|||||||
|
|
||||||
// Clone nested objects
|
// Clone nested objects
|
||||||
export function cloneDeep(object) {
|
export function cloneDeep(object) {
|
||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a nested value in an object
|
// Get a nested value in an object
|
||||||
export function getDeep(object, path) {
|
export function getDeep(object, path) {
|
||||||
return path.split('.').reduce((obj, key) => obj && obj[key], object);
|
return path.split('.').reduce((obj, key) => obj && obj[key], object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deep extend destination object with N more objects
|
// Deep extend destination object with N more objects
|
||||||
export function extend(target = {}, ...sources) {
|
export function extend(target = {}, ...sources) {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = sources.shift();
|
||||||
|
|
||||||
|
if (!is.object(source)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(source).forEach(key => {
|
||||||
|
if (is.object(source[key])) {
|
||||||
|
if (!Object.keys(target).includes(key)) {
|
||||||
|
Object.assign(target, { [key]: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(target[key], source[key]);
|
||||||
|
} else {
|
||||||
|
Object.assign(target, { [key]: source[key] });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const source = sources.shift();
|
return extend(target, ...sources);
|
||||||
|
|
||||||
if (!is.object(source)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(source).forEach(key => {
|
|
||||||
if (is.object(source[key])) {
|
|
||||||
if (!Object.keys(target).includes(key)) {
|
|
||||||
Object.assign(target, { [key]: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
extend(target[key], source[key]);
|
|
||||||
} else {
|
|
||||||
Object.assign(target, { [key]: source[key] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return extend(target, ...sources);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import is from './is';
|
|||||||
* @param {Object} value An object that may or may not be `Promise`-like.
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
*/
|
*/
|
||||||
export function silencePromise(value) {
|
export function silencePromise(value) {
|
||||||
if (is.promise(value)) {
|
if (is.promise(value)) {
|
||||||
value.then(null, () => {});
|
value.then(null, () => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { silencePromise };
|
export default { silencePromise };
|
||||||
|
|||||||
+35
-40
@@ -6,80 +6,75 @@ import is from './is';
|
|||||||
|
|
||||||
// Generate a random ID
|
// Generate a random ID
|
||||||
export function generateId(prefix) {
|
export function generateId(prefix) {
|
||||||
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format string
|
// Format string
|
||||||
export function format(input, ...args) {
|
export function format(input, ...args) {
|
||||||
if (is.empty(input)) {
|
if (is.empty(input)) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString());
|
return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get percentage
|
// Get percentage
|
||||||
export function getPercentage(current, max) {
|
export function getPercentage(current, max) {
|
||||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((current / max) * 100).toFixed(2);
|
return ((current / max) * 100).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all occurances of a string in a string
|
// Replace all occurances of a string in a string
|
||||||
export function replaceAll(input = '', find = '', replace = '') {
|
export const replaceAll = (input = '', find = '', replace = '') =>
|
||||||
return input.replace(
|
input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'),
|
|
||||||
replace.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to title case
|
// Convert to title case
|
||||||
export function toTitleCase(input = '') {
|
export const toTitleCase = (input = '') =>
|
||||||
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
||||||
}
|
|
||||||
|
|
||||||
// Convert string to pascalCase
|
// Convert string to pascalCase
|
||||||
export function toPascalCase(input = '') {
|
export function toPascalCase(input = '') {
|
||||||
let string = input.toString();
|
let string = input.toString();
|
||||||
|
|
||||||
// Convert kebab case
|
// Convert kebab case
|
||||||
string = replaceAll(string, '-', ' ');
|
string = replaceAll(string, '-', ' ');
|
||||||
|
|
||||||
// Convert snake case
|
// Convert snake case
|
||||||
string = replaceAll(string, '_', ' ');
|
string = replaceAll(string, '_', ' ');
|
||||||
|
|
||||||
// Convert to title case
|
// Convert to title case
|
||||||
string = toTitleCase(string);
|
string = toTitleCase(string);
|
||||||
|
|
||||||
// Convert to pascal case
|
// Convert to pascal case
|
||||||
return replaceAll(string, ' ', '');
|
return replaceAll(string, ' ', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert string to pascalCase
|
// Convert string to pascalCase
|
||||||
export function toCamelCase(input = '') {
|
export function toCamelCase(input = '') {
|
||||||
let string = input.toString();
|
let string = input.toString();
|
||||||
|
|
||||||
// Convert to pascal case
|
// Convert to pascal case
|
||||||
string = toPascalCase(string);
|
string = toPascalCase(string);
|
||||||
|
|
||||||
// Convert first character to lowercase
|
// Convert first character to lowercase
|
||||||
return string.charAt(0).toLowerCase() + string.slice(1);
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove HTML from a string
|
// Remove HTML from a string
|
||||||
export function stripHTML(source) {
|
export function stripHTML(source) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
fragment.appendChild(element);
|
fragment.appendChild(element);
|
||||||
element.innerHTML = source;
|
element.innerHTML = source;
|
||||||
return fragment.firstChild.innerText;
|
return fragment.firstChild.innerText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like outerHTML, but also works for DocumentFragment
|
// Like outerHTML, but also works for DocumentFragment
|
||||||
export function getHTML(element) {
|
export function getHTML(element) {
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.appendChild(element);
|
wrapper.appendChild(element);
|
||||||
return wrapper.innerHTML;
|
return wrapper.innerHTML;
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-46
@@ -5,74 +5,74 @@
|
|||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
export function validateRatio(input) {
|
export function validateRatio(input) {
|
||||||
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
|
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = is.array(input) ? input : input.split(':');
|
const ratio = is.array(input) ? input : input.split(':');
|
||||||
|
|
||||||
return ratio.map(Number).every(is.number);
|
return ratio.map(Number).every(is.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reduceAspectRatio(ratio) {
|
export function reduceAspectRatio(ratio) {
|
||||||
if (!is.array(ratio) || !ratio.every(is.number)) {
|
if (!is.array(ratio) || !ratio.every(is.number)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [width, height] = ratio;
|
const [width, height] = ratio;
|
||||||
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
|
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
|
||||||
const divider = getDivider(width, height);
|
const divider = getDivider(width, height);
|
||||||
|
|
||||||
return [width / divider, height / divider];
|
return [width / divider, height / divider];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAspectRatio(input) {
|
export function getAspectRatio(input) {
|
||||||
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
||||||
// Try provided ratio
|
// Try provided ratio
|
||||||
let ratio = parse(input);
|
let ratio = parse(input);
|
||||||
|
|
||||||
// Get from config
|
// Get from config
|
||||||
if (ratio === null) {
|
if (ratio === null) {
|
||||||
ratio = parse(this.config.ratio);
|
ratio = parse(this.config.ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get from embed
|
// Get from embed
|
||||||
if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {
|
if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {
|
||||||
({ ratio } = this.embed);
|
({ ratio } = this.embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get from HTML5 video
|
// Get from HTML5 video
|
||||||
if (ratio === null && this.isHTML5) {
|
if (ratio === null && this.isHTML5) {
|
||||||
const { videoWidth, videoHeight } = this.media;
|
const { videoWidth, videoHeight } = this.media;
|
||||||
ratio = reduceAspectRatio([videoWidth, videoHeight]);
|
ratio = reduceAspectRatio([videoWidth, videoHeight]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ratio;
|
return ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set aspect ratio for responsive container
|
// Set aspect ratio for responsive container
|
||||||
export function setAspectRatio(input) {
|
export function setAspectRatio(input) {
|
||||||
if (!this.isVideo) {
|
if (!this.isVideo) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wrapper } = this.elements;
|
const { wrapper } = this.elements;
|
||||||
const ratio = getAspectRatio.call(this, input);
|
const ratio = getAspectRatio.call(this, input);
|
||||||
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
||||||
const padding = (100 / w) * h;
|
const padding = (100 / w) * h;
|
||||||
|
|
||||||
wrapper.style.paddingBottom = `${padding}%`;
|
wrapper.style.paddingBottom = `${padding}%`;
|
||||||
|
|
||||||
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && this.supported.ui) {
|
||||||
const height = 240;
|
const height = 240;
|
||||||
const offset = (height - padding) / (height / 50);
|
const offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = `translateY(-${offset}%)`;
|
this.media.style.transform = `translateY(-${offset}%)`;
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { padding, ratio };
|
return { padding, ratio };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setAspectRatio };
|
export default { setAspectRatio };
|
||||||
|
|||||||
+18
-18
@@ -11,25 +11,25 @@ export const getSeconds = value => Math.trunc(value % 60, 10);
|
|||||||
|
|
||||||
// Format time to UI friendly string
|
// Format time to UI friendly string
|
||||||
export function formatTime(time = 0, displayHours = false, inverted = false) {
|
export function formatTime(time = 0, displayHours = false, inverted = false) {
|
||||||
// Bail if the value isn't a number
|
// Bail if the value isn't a number
|
||||||
if (!is.number(time)) {
|
if (!is.number(time)) {
|
||||||
return formatTime(undefined, displayHours, inverted);
|
return formatTime(undefined, displayHours, inverted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format time component to add leading zero
|
// Format time component to add leading zero
|
||||||
const format = value => `0${value}`.slice(-2);
|
const format = value => `0${value}`.slice(-2);
|
||||||
// Breakdown to hours, mins, secs
|
// Breakdown to hours, mins, secs
|
||||||
let hours = getHours(time);
|
let hours = getHours(time);
|
||||||
const mins = getMinutes(time);
|
const mins = getMinutes(time);
|
||||||
const secs = getSeconds(time);
|
const secs = getSeconds(time);
|
||||||
|
|
||||||
// Do we need to display hours?
|
// Do we need to display hours?
|
||||||
if (displayHours || hours > 0) {
|
if (displayHours || hours > 0) {
|
||||||
hours = `${hours}:`;
|
hours = `${hours}:`;
|
||||||
} else {
|
} else {
|
||||||
hours = '';
|
hours = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-18
@@ -10,30 +10,30 @@ import is from './is';
|
|||||||
* @param {Boolean} safe - failsafe parsing
|
* @param {Boolean} safe - failsafe parsing
|
||||||
*/
|
*/
|
||||||
export function parseUrl(input, safe = true) {
|
export function parseUrl(input, safe = true) {
|
||||||
let url = input;
|
let url = input;
|
||||||
|
|
||||||
if (safe) {
|
if (safe) {
|
||||||
const parser = document.createElement('a');
|
const parser = document.createElement('a');
|
||||||
parser.href = url;
|
parser.href = url;
|
||||||
url = parser.href;
|
url = parser.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URL(url);
|
return new URL(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert object to URLSearchParams
|
// Convert object to URLSearchParams
|
||||||
export function buildUrlParams(input) {
|
export function buildUrlParams(input) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (is.object(input)) {
|
if (is.object(input)) {
|
||||||
Object.entries(input).forEach(([key, value]) => {
|
Object.entries(input).forEach(([key, value]) => {
|
||||||
params.set(key, value);
|
params.set(key, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-47
@@ -4,66 +4,66 @@
|
|||||||
|
|
||||||
// Base
|
// Base
|
||||||
.plyr {
|
.plyr {
|
||||||
@include plyr-font-smoothing($plyr-font-smoothing);
|
@include plyr-font-smoothing($plyr-font-smoothing);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-family: var(--plyr-font-family);
|
font-family: $plyr-font-family;
|
||||||
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
||||||
font-weight: var(--plyr-font-weight-regular);
|
font-weight: $plyr-font-weight-regular;
|
||||||
|
height: 100%;
|
||||||
|
line-height: $plyr-line-height;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 200px;
|
||||||
|
position: relative;
|
||||||
|
text-shadow: none;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
z-index: 0; // Force any border radius
|
||||||
|
|
||||||
|
// Media elements
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: var(--plyr-line-height);
|
width: 100%;
|
||||||
max-width: 100%;
|
}
|
||||||
min-width: 200px;
|
|
||||||
position: relative;
|
|
||||||
text-shadow: none;
|
|
||||||
transition: box-shadow 0.3s ease;
|
|
||||||
z-index: 0; // Force any border radius
|
|
||||||
|
|
||||||
// Media elements
|
button {
|
||||||
video,
|
font: inherit;
|
||||||
audio,
|
line-height: inherit;
|
||||||
iframe {
|
width: auto;
|
||||||
display: block;
|
}
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
// Ignore focus
|
||||||
font: inherit;
|
&:focus {
|
||||||
line-height: inherit;
|
outline: 0;
|
||||||
width: auto;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore focus
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// border-box everything
|
// border-box everything
|
||||||
// http://paulirish.com/2012/box-sizing-border-box-ftw/
|
// http://paulirish.com/2012/box-sizing-border-box-ftw/
|
||||||
@if $plyr-border-box {
|
@if $plyr-border-box {
|
||||||
.plyr--full-ui {
|
.plyr--full-ui {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::after,
|
*::after,
|
||||||
*::before {
|
*::before {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix 300ms delay
|
// Fix 300ms delay
|
||||||
@if $plyr-touch-action {
|
@if $plyr-touch-action {
|
||||||
.plyr--full-ui {
|
.plyr--full-ui {
|
||||||
a,
|
a,
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
label {
|
label {
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__badge {
|
.plyr__badge {
|
||||||
background: var(--plyr-badge-background, $plyr-badge-background);
|
background: $plyr-badge-background;
|
||||||
border-radius: 2px;
|
border-radius: $plyr-badge-border-radius;
|
||||||
color: var(--plyr-badge-color, $plyr-badge-color);
|
color: $plyr-badge-color;
|
||||||
font-size: var(--plyr-font-size-badge, $plyr-font-size-badge);
|
font-size: $plyr-font-size-badge;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,57 +4,55 @@
|
|||||||
|
|
||||||
// Hide default captions
|
// Hide default captions
|
||||||
.plyr--full-ui ::-webkit-media-text-track-container {
|
.plyr--full-ui ::-webkit-media-text-track-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__captions {
|
.plyr__captions {
|
||||||
animation: plyr-fade-in 0.3s ease;
|
animation: plyr-fade-in 0.3s ease;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
display: none;
|
||||||
|
font-size: $plyr-font-size-captions-small;
|
||||||
|
left: 0;
|
||||||
|
padding: $plyr-control-spacing;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.4s ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.plyr__caption {
|
||||||
|
background: $plyr-captions-background;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
color: $plyr-captions-text-color;
|
||||||
|
line-height: 185%;
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
// Firefox adds a <div> when using getCueAsHTML()
|
||||||
|
div {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span:empty {
|
||||||
display: none;
|
display: none;
|
||||||
font-size: $plyr-font-size-captions-small;
|
}
|
||||||
left: 0;
|
|
||||||
padding: $plyr-control-spacing;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
transition: transform 0.4s ease-in-out;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.plyr__caption {
|
@media (min-width: $plyr-bp-sm) {
|
||||||
background: $plyr-captions-background;
|
font-size: $plyr-font-size-captions-base;
|
||||||
background: var(--plyr-captions-background, $plyr-captions-background);
|
padding: calc(#{$plyr-control-spacing} * 2);
|
||||||
border-radius: 2px;
|
}
|
||||||
box-decoration-break: clone;
|
|
||||||
color: $plyr-captions-text-color;
|
|
||||||
color: var(--plyr-captions-text-color, $plyr-captions-text-color);
|
|
||||||
line-height: 185%;
|
|
||||||
padding: 0.2em 0.5em;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
|
|
||||||
// Firefox adds a <div> when using getCueAsHTML()
|
@media (min-width: $plyr-bp-md) {
|
||||||
div {
|
font-size: $plyr-font-size-captions-medium;
|
||||||
display: inline;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-sm) {
|
|
||||||
font-size: $plyr-font-size-captions-base;
|
|
||||||
padding: ($plyr-control-spacing * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-md) {
|
|
||||||
font-size: $plyr-font-size-captions-medium;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--captions-active .plyr__captions {
|
.plyr--captions-active .plyr__captions {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the lower controls are shown and not empty
|
// If the lower controls are shown and not empty
|
||||||
.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty) ~ .plyr__captions {
|
.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty) ~ .plyr__captions {
|
||||||
transform: translateY(-($plyr-control-spacing * 4));
|
transform: translateY(calc(#{$plyr-control-spacing} * -4));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,45 +3,44 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__control {
|
.plyr__control {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $plyr-control-radius;
|
border-radius: $plyr-control-radius;
|
||||||
border-radius: var(--plyr-control-radius, $plyr-control-radius);
|
color: inherit;
|
||||||
color: inherit;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
flex-shrink: 0;
|
||||||
flex-shrink: 0;
|
overflow: visible; // IE11
|
||||||
overflow: visible; // IE11
|
padding: $plyr-control-padding;
|
||||||
padding: var(--plyr-control-padding, $plyr-control-padding);
|
position: relative;
|
||||||
position: relative;
|
transition: all 0.3s ease;
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: block;
|
display: block;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
height: var(--plyr-control-icon-size, $plyr-control-icon-size);
|
height: $plyr-control-icon-size;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
width: var(--plyr-control-icon-size, $plyr-control-icon-size);
|
width: $plyr-control-icon-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default focus
|
// Default focus
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab focus
|
// Tab focus
|
||||||
&.plyr__tab-focus {
|
&.plyr__tab-focus {
|
||||||
@include plyr-tab-focus();
|
@include plyr-tab-focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any link styling
|
// Remove any link styling
|
||||||
a.plyr__control {
|
a.plyr__control {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&::after,
|
&::after,
|
||||||
&::before {
|
&::before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change icons on state change
|
// Change icons on state change
|
||||||
@@ -49,5 +48,5 @@ a.plyr__control {
|
|||||||
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
||||||
.plyr__control:not(.plyr__control--pressed) .label--pressed,
|
.plyr__control:not(.plyr__control--pressed) .label--pressed,
|
||||||
.plyr__control.plyr__control--pressed .label--not-pressed {
|
.plyr__control.plyr__control--pressed .label--not-pressed {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,49 +4,49 @@
|
|||||||
|
|
||||||
// Hide native controls
|
// Hide native controls
|
||||||
.plyr--full-ui ::-webkit-media-controls {
|
.plyr--full-ui ::-webkit-media-controls {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playback controls
|
// Playback controls
|
||||||
.plyr__controls {
|
.plyr__controls {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.plyr__progress__container {
|
.plyr__progress__container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0; // Fix for Edge issue where content would overflow
|
min-width: 0; // Fix for Edge issue where content would overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacing
|
||||||
|
.plyr__controls__item {
|
||||||
|
margin-left: calc(#{$plyr-control-spacing} / 4);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacing
|
&.plyr__progress__container {
|
||||||
.plyr__controls__item {
|
padding-left: calc(#{$plyr-control-spacing} / 4);
|
||||||
margin-left: ($plyr-control-spacing / 4);
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__progress__container {
|
|
||||||
padding-left: ($plyr-control-spacing / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__time {
|
|
||||||
padding: 0 ($plyr-control-spacing / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__progress__container:first-child,
|
|
||||||
&.plyr__time:first-child,
|
|
||||||
&.plyr__time + .plyr__time {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide empty controls
|
&.plyr__time {
|
||||||
&:empty {
|
padding: 0 calc(#{$plyr-control-spacing} / 2);
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.plyr__progress__container:first-child,
|
||||||
|
&.plyr__time:first-child,
|
||||||
|
&.plyr__time + .plyr__time {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide empty controls
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some options are hidden by default
|
// Some options are hidden by default
|
||||||
@@ -54,11 +54,11 @@
|
|||||||
.plyr [data-plyr='pip'],
|
.plyr [data-plyr='pip'],
|
||||||
.plyr [data-plyr='airplay'],
|
.plyr [data-plyr='airplay'],
|
||||||
.plyr [data-plyr='fullscreen'] {
|
.plyr [data-plyr='fullscreen'] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.plyr--captions-enabled [data-plyr='captions'],
|
.plyr--captions-enabled [data-plyr='captions'],
|
||||||
.plyr--pip-supported [data-plyr='pip'],
|
.plyr--pip-supported [data-plyr='pip'],
|
||||||
.plyr--airplay-supported [data-plyr='airplay'],
|
.plyr--airplay-supported [data-plyr='airplay'],
|
||||||
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
|
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-179
@@ -3,198 +3,200 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__menu {
|
.plyr__menu {
|
||||||
display: flex; // Edge fix
|
display: flex; // Edge fix
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// Animate the icon
|
// Animate the icon
|
||||||
.plyr__control svg {
|
.plyr__control svg {
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
.plyr__control[aria-expanded='true'] {
|
.plyr__control[aria-expanded='true'] {
|
||||||
svg {
|
svg {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
|
||||||
|
|
||||||
// Hide tooltip
|
|
||||||
.plyr__tooltip {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The actual menu container
|
// Hide tooltip
|
||||||
&__container {
|
.plyr__tooltip {
|
||||||
animation: plyr-popup 0.2s ease;
|
display: none;
|
||||||
background: $plyr-menu-background;
|
}
|
||||||
border-radius: 4px;
|
}
|
||||||
bottom: 100%;
|
|
||||||
box-shadow: $plyr-menu-shadow;
|
// The actual menu container
|
||||||
color: $plyr-menu-color;
|
&__container {
|
||||||
font-size: $plyr-font-size-base;
|
animation: plyr-popup 0.2s ease;
|
||||||
margin-bottom: 10px;
|
background: $plyr-menu-background;
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: 100%;
|
||||||
|
box-shadow: $plyr-menu-shadow;
|
||||||
|
color: $plyr-menu-color;
|
||||||
|
font-size: $plyr-font-size-base;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: absolute;
|
||||||
|
right: -3px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 3;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow
|
||||||
|
&::after {
|
||||||
|
border: 4px solid transparent;
|
||||||
|
border-top-color: $plyr-menu-background;
|
||||||
|
content: '';
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 100%;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role='menu'] {
|
||||||
|
padding: $plyr-control-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role='menuitem'],
|
||||||
|
[role='menuitemradio'] {
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options
|
||||||
|
.plyr__control {
|
||||||
|
align-items: center;
|
||||||
|
color: $plyr-menu-color;
|
||||||
|
display: flex;
|
||||||
|
font-size: $plyr-font-size-menu;
|
||||||
|
padding-bottom: calc(#{$plyr-control-padding} / 1.5);
|
||||||
|
padding-left: calc(#{$plyr-control-padding} * 1.5);
|
||||||
|
padding-right: calc(#{$plyr-control-padding} * 1.5);
|
||||||
|
padding-top: calc(#{$plyr-control-padding} / 1.5);
|
||||||
|
user-select: none;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
align-items: inherit;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: 4px solid transparent;
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -3px;
|
top: 50%;
|
||||||
text-align: left;
|
transform: translateY(-50%);
|
||||||
white-space: nowrap;
|
}
|
||||||
z-index: 3;
|
|
||||||
|
|
||||||
> div {
|
&--forward {
|
||||||
overflow: hidden;
|
padding-right: calc(#{$plyr-control-padding} * 4);
|
||||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arrow
|
|
||||||
&::after {
|
&::after {
|
||||||
border: 4px solid transparent;
|
border-left-color: $plyr-menu-arrow-color;
|
||||||
border-top-color: $plyr-menu-background;
|
right: 5px;
|
||||||
content: '';
|
|
||||||
height: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
top: 100%;
|
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[role='menu'] {
|
&.plyr__tab-focus::after,
|
||||||
padding: $plyr-control-padding;
|
&:hover::after {
|
||||||
|
border-left-color: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--back {
|
||||||
|
font-weight: $plyr-font-weight-regular;
|
||||||
|
margin: $plyr-control-padding;
|
||||||
|
margin-bottom: calc(#{$plyr-control-padding} / 2);
|
||||||
|
padding-left: calc(#{$plyr-control-padding} * 4);
|
||||||
|
position: relative;
|
||||||
|
width: calc(100% - (#{$plyr-control-padding} * 2));
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-right-color: $plyr-menu-arrow-color;
|
||||||
|
left: $plyr-control-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
[role='menuitem'],
|
&::before {
|
||||||
[role='menuitemradio'] {
|
background: $plyr-menu-border-color;
|
||||||
margin-top: 2px;
|
box-shadow: 0 1px 0 $plyr-menu-border-shadow-color;
|
||||||
|
content: '';
|
||||||
&:first-child {
|
height: 1px;
|
||||||
margin-top: 0;
|
left: 0;
|
||||||
}
|
margin-top: calc(#{$plyr-control-padding} / 2);
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options
|
&.plyr__tab-focus::after,
|
||||||
.plyr__control {
|
&:hover::after {
|
||||||
align-items: center;
|
border-right-color: currentColor;
|
||||||
color: $plyr-menu-color;
|
|
||||||
display: flex;
|
|
||||||
font-size: $plyr-font-size-menu;
|
|
||||||
padding: ceil($plyr-control-padding / 2) ceil($plyr-control-padding * 1.5);
|
|
||||||
user-select: none;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
align-items: inherit;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border: 4px solid transparent;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--forward {
|
|
||||||
padding-right: ceil($plyr-control-padding * 4);
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border-left-color: rgba($plyr-menu-color, 0.8);
|
|
||||||
right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__tab-focus::after,
|
|
||||||
&:hover::after {
|
|
||||||
border-left-color: currentColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--back {
|
|
||||||
$horizontal-padding: ($plyr-control-padding * 2);
|
|
||||||
font-weight: $plyr-font-weight-regular;
|
|
||||||
margin: $plyr-control-padding;
|
|
||||||
margin-bottom: floor($plyr-control-padding / 2);
|
|
||||||
padding-left: ceil($plyr-control-padding * 4);
|
|
||||||
position: relative;
|
|
||||||
width: calc(100% - #{$horizontal-padding});
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border-right-color: rgba($plyr-menu-color, 0.8);
|
|
||||||
left: $plyr-control-padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background: $plyr-menu-border-color;
|
|
||||||
box-shadow: 0 1px 0 $plyr-menu-border-shadow-color;
|
|
||||||
content: '';
|
|
||||||
height: 1px;
|
|
||||||
left: 0;
|
|
||||||
margin-top: ceil($plyr-control-padding / 2);
|
|
||||||
overflow: hidden;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__tab-focus::after,
|
|
||||||
&:hover::after {
|
|
||||||
border-right-color: currentColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plyr__control[role='menuitemradio'] {
|
|
||||||
padding-left: $plyr-control-padding;
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background: rgba(#000, 0.1);
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 16px;
|
|
||||||
margin-right: $plyr-control-spacing;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
background: #fff;
|
|
||||||
border: 0;
|
|
||||||
height: 6px;
|
|
||||||
left: 12px;
|
|
||||||
opacity: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%) scale(0);
|
|
||||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[aria-checked='true'] {
|
|
||||||
&::before {
|
|
||||||
background: var(--plyr-color-main, $plyr-color-main);
|
|
||||||
}
|
|
||||||
&::after {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(-50%) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__tab-focus::before,
|
|
||||||
&:hover::before {
|
|
||||||
background: rgba(#000, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option value
|
|
||||||
.plyr__menu__value {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: -($plyr-control-padding - 2);
|
|
||||||
overflow: hidden;
|
|
||||||
padding-left: ceil($plyr-control-padding * 3.5);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plyr__control[role='menuitemradio'] {
|
||||||
|
padding-left: $plyr-control-padding;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: rgba(#000, 0.1);
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: $plyr-control-spacing;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: #fff;
|
||||||
|
border: 0;
|
||||||
|
height: 6px;
|
||||||
|
left: 12px;
|
||||||
|
opacity: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) scale(0);
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-checked='true'] {
|
||||||
|
&::before {
|
||||||
|
background: $plyr-control-toggle-checked-background;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-50%) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__tab-focus::before,
|
||||||
|
&:hover::before {
|
||||||
|
background: rgba($plyr-color-gray-900, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option value
|
||||||
|
.plyr__menu__value {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: calc((#{$plyr-control-padding} - 2) * -1);
|
||||||
|
overflow: hidden;
|
||||||
|
padding-left: calc(#{$plyr-control-padding} * 3.5);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__poster {
|
.plyr__poster {
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--stopped.plyr__poster-enabled .plyr__poster {
|
.plyr--stopped.plyr__poster-enabled .plyr__poster {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,89 +6,89 @@
|
|||||||
$plyr-progress-offset: $plyr-range-thumb-height;
|
$plyr-progress-offset: $plyr-range-thumb-height;
|
||||||
|
|
||||||
.plyr__progress {
|
.plyr__progress {
|
||||||
left: $plyr-progress-offset / 2;
|
left: calc(#{$plyr-progress-offset} * 0.5);
|
||||||
margin-right: $plyr-progress-offset;
|
margin-right: $plyr-progress-offset;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input[type='range'],
|
||||||
|
&__buffer {
|
||||||
|
margin-left: calc(#{$plyr-progress-offset} * -0.5);
|
||||||
|
margin-right: calc(#{$plyr-progress-offset} * -0.5);
|
||||||
|
width: calc(100% + #{$plyr-progress-offset});
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'] {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
input[type='range'],
|
// Seek tooltip to show time
|
||||||
&__buffer {
|
.plyr__tooltip {
|
||||||
margin-left: -($plyr-progress-offset / 2);
|
font-size: $plyr-font-size-time;
|
||||||
margin-right: -($plyr-progress-offset / 2);
|
left: 0;
|
||||||
width: calc(100% + #{$plyr-progress-offset});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
input[type='range'] {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek tooltip to show time
|
|
||||||
.plyr__tooltip {
|
|
||||||
font-size: $plyr-font-size-time;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__progress__buffer {
|
.plyr__progress__buffer {
|
||||||
-webkit-appearance: none; /* stylelint-disable-line */
|
-webkit-appearance: none; /* stylelint-disable-line */
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 100px;
|
||||||
|
height: $plyr-range-track-height;
|
||||||
|
left: 0;
|
||||||
|
margin-top: calc((#{$plyr-range-track-height} / 2) * -1);
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
|
||||||
|
&::-webkit-progress-bar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
background: currentColor;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
height: $plyr-range-track-height;
|
min-width: $plyr-range-track-height;
|
||||||
left: 0;
|
transition: width 0.2s ease;
|
||||||
margin-top: -($plyr-range-track-height / 2);
|
}
|
||||||
padding: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
|
|
||||||
&::-webkit-progress-bar {
|
// Mozilla
|
||||||
background: transparent;
|
&::-moz-progress-bar {
|
||||||
}
|
background: currentColor;
|
||||||
|
border-radius: 100px;
|
||||||
|
min-width: $plyr-range-track-height;
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-progress-value {
|
// Microsoft
|
||||||
background: currentColor;
|
&::-ms-fill {
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
min-width: $plyr-range-track-height;
|
transition: width 0.2s ease;
|
||||||
transition: width 0.2s ease;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Mozilla
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
background: currentColor;
|
|
||||||
border-radius: 100px;
|
|
||||||
min-width: $plyr-range-track-height;
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft
|
|
||||||
&::-ms-fill {
|
|
||||||
border-radius: 100px;
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
.plyr--loading .plyr__progress__buffer {
|
.plyr--loading .plyr__progress__buffer {
|
||||||
animation: plyr-progress 1s linear infinite;
|
animation: plyr-progress 1s linear infinite;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
-45deg,
|
-45deg,
|
||||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 25%,
|
$plyr-progress-loading-background 25%,
|
||||||
transparent 25%,
|
transparent 25%,
|
||||||
transparent 50%,
|
transparent 50%,
|
||||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 50%,
|
$plyr-progress-loading-background 50%,
|
||||||
var(--plyr-progress-loading-background, $plyr-progress-loading-background) 75%,
|
$plyr-progress-loading-background 75%,
|
||||||
transparent 75%,
|
transparent 75%,
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
background-size: $plyr-progress-loading-size $plyr-progress-loading-size;
|
background-size: $plyr-progress-loading-size $plyr-progress-loading-size;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--video.plyr--loading .plyr__progress__buffer {
|
.plyr--video.plyr--loading .plyr__progress__buffer {
|
||||||
background-color: $plyr-video-progress-buffered-background;
|
background-color: $plyr-video-progress-buffered-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--audio.plyr--loading .plyr__progress__buffer {
|
.plyr--audio.plyr--loading .plyr__progress__buffer {
|
||||||
background-color: $plyr-audio-progress-buffered-background;
|
background-color: $plyr-audio-progress-buffered-background;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,92 +3,92 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr--full-ui input[type='range'] {
|
.plyr--full-ui input[type='range'] {
|
||||||
// WebKit
|
// WebKit
|
||||||
|
-webkit-appearance: none; /* stylelint-disable-line */
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: calc(#{$plyr-range-thumb-height} * 2);
|
||||||
|
// color is used in JS to populate lower fill for WebKit
|
||||||
|
color: $plyr-range-fill-background;
|
||||||
|
display: block;
|
||||||
|
height: $plyr-range-max-height;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
@include plyr-range-track();
|
||||||
|
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
@include plyr-range-thumb();
|
||||||
-webkit-appearance: none; /* stylelint-disable-line */
|
-webkit-appearance: none; /* stylelint-disable-line */
|
||||||
background: transparent;
|
margin-top: calc(((#{$plyr-range-thumb-height} - #{$plyr-range-track-height}) / 2) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mozilla
|
||||||
|
&::-moz-range-track {
|
||||||
|
@include plyr-range-track();
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
@include plyr-range-thumb();
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-progress {
|
||||||
|
background: currentColor;
|
||||||
|
border-radius: calc(#{$plyr-range-track-height} / 2);
|
||||||
|
height: $plyr-range-track-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microsoft
|
||||||
|
&::-ms-track {
|
||||||
|
@include plyr-range-track();
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-fill-upper {
|
||||||
|
@include plyr-range-track();
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-fill-lower {
|
||||||
|
@include plyr-range-track();
|
||||||
|
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-thumb {
|
||||||
|
@include plyr-range-thumb();
|
||||||
|
// For some reason, Edge uses the -webkit margin above
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus styles
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-focus-outer {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: ($plyr-range-thumb-height * 2);
|
}
|
||||||
// color is used in JS to populate lower fill for WebKit
|
|
||||||
color: $plyr-range-fill-background;
|
|
||||||
display: block;
|
|
||||||
height: $plyr-range-max-height;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
transition: box-shadow 0.3s ease;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
|
&.plyr__tab-focus {
|
||||||
&::-webkit-slider-runnable-track {
|
&::-webkit-slider-runnable-track {
|
||||||
@include plyr-range-track();
|
@include plyr-tab-focus();
|
||||||
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
|
||||||
@include plyr-range-thumb();
|
|
||||||
-webkit-appearance: none; /* stylelint-disable-line */
|
|
||||||
margin-top: -(($plyr-range-thumb-height - $plyr-range-track-height) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mozilla
|
|
||||||
&::-moz-range-track {
|
&::-moz-range-track {
|
||||||
@include plyr-range-track();
|
@include plyr-tab-focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
|
||||||
@include plyr-range-thumb();
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-progress {
|
|
||||||
background: currentColor;
|
|
||||||
border-radius: ($plyr-range-track-height / 2);
|
|
||||||
height: $plyr-range-track-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft
|
|
||||||
&::-ms-track {
|
&::-ms-track {
|
||||||
@include plyr-range-track();
|
@include plyr-tab-focus();
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-fill-upper {
|
|
||||||
@include plyr-range-track();
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-fill-lower {
|
|
||||||
@include plyr-range-track();
|
|
||||||
|
|
||||||
background: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-thumb {
|
|
||||||
@include plyr-range-thumb();
|
|
||||||
// For some reason, Edge uses the -webkit margin above
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-tooltip {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus styles
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-outer {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__tab-focus {
|
|
||||||
&::-webkit-slider-runnable-track {
|
|
||||||
@include plyr-tab-focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-track {
|
|
||||||
@include plyr-tab-focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-track {
|
|
||||||
@include plyr-tab-focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__time {
|
.plyr__time {
|
||||||
font-size: $plyr-font-size-time;
|
font-size: $plyr-font-size-time;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media duration hidden on small screens
|
// Media duration hidden on small screens
|
||||||
.plyr__time + .plyr__time {
|
.plyr__time + .plyr__time {
|
||||||
// Add a slash in before
|
// Add a slash in before
|
||||||
&::before {
|
&::before {
|
||||||
content: '\2044';
|
content: '\2044';
|
||||||
margin-right: $plyr-control-spacing;
|
margin-right: $plyr-control-spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $plyr-bp-sm-max) {
|
@media (max-width: calc(#{$plyr-bp-md} - 1)) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,86 +3,86 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__tooltip {
|
.plyr__tooltip {
|
||||||
background: $plyr-tooltip-background;
|
background: $plyr-tooltip-background;
|
||||||
border-radius: $plyr-tooltip-radius;
|
border-radius: $plyr-tooltip-radius;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
box-shadow: $plyr-tooltip-shadow;
|
box-shadow: $plyr-tooltip-shadow;
|
||||||
color: $plyr-tooltip-color;
|
color: $plyr-tooltip-color;
|
||||||
font-size: $plyr-font-size-small;
|
font-size: $plyr-font-size-small;
|
||||||
font-weight: $plyr-font-weight-regular;
|
font-weight: $plyr-font-weight-regular;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-bottom: ($plyr-tooltip-padding * 2);
|
margin-bottom: calc(#{$plyr-tooltip-padding} * 2);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
padding: $plyr-tooltip-padding ($plyr-tooltip-padding * 1.5);
|
padding: $plyr-tooltip-padding calc(#{$plyr-tooltip-padding} * 1.5);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, 10px) scale(0.8);
|
transform: translate(-50%, 10px) scale(0.8);
|
||||||
transform-origin: 50% 100%;
|
transform-origin: 50% 100%;
|
||||||
transition: transform 0.2s 0.1s ease, opacity 0.2s 0.1s ease;
|
transition: transform 0.2s 0.1s ease, opacity 0.2s 0.1s ease;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
// The background triangle
|
// The background triangle
|
||||||
&::before {
|
&::before {
|
||||||
border-left: $plyr-tooltip-arrow-size solid transparent;
|
border-left: $plyr-tooltip-arrow-size solid transparent;
|
||||||
border-right: $plyr-tooltip-arrow-size solid transparent;
|
border-right: $plyr-tooltip-arrow-size solid transparent;
|
||||||
border-top: $plyr-tooltip-arrow-size solid $plyr-tooltip-background;
|
border-top: $plyr-tooltip-arrow-size solid $plyr-tooltip-background;
|
||||||
bottom: -$plyr-tooltip-arrow-size;
|
bottom: calc(#{$plyr-tooltip-arrow-size} * -1);
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 0;
|
width: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Displaying
|
// Displaying
|
||||||
.plyr .plyr__control:hover .plyr__tooltip,
|
.plyr .plyr__control:hover .plyr__tooltip,
|
||||||
.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,
|
.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,
|
||||||
.plyr__tooltip--visible {
|
.plyr__tooltip--visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translate(-50%, 0) scale(1);
|
transform: translate(-50%, 0) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr .plyr__control:hover .plyr__tooltip {
|
.plyr .plyr__control:hover .plyr__tooltip {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First tooltip
|
// First tooltip
|
||||||
.plyr__controls > .plyr__control:first-child .plyr__tooltip,
|
.plyr__controls > .plyr__control:first-child .plyr__tooltip,
|
||||||
.plyr__controls > .plyr__control:first-child + .plyr__control .plyr__tooltip {
|
.plyr__controls > .plyr__control:first-child + .plyr__control .plyr__tooltip {
|
||||||
left: 0;
|
left: 0;
|
||||||
transform: translate(0, 10px) scale(0.8);
|
transform: translate(0, 10px) scale(0.8);
|
||||||
transform-origin: 0 100%;
|
transform-origin: 0 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
left: ($plyr-control-icon-size / 2) + $plyr-control-padding;
|
left: calc((#{$plyr-control-icon-size} / 2) + #{$plyr-control-padding});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last tooltip
|
// Last tooltip
|
||||||
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
|
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translate(0, 10px) scale(0.8);
|
transform: translate(0, 10px) scale(0.8);
|
||||||
transform-origin: 100% 100%;
|
transform-origin: 100% 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: ($plyr-control-icon-size / 2) + $plyr-control-padding;
|
right: calc((#{$plyr-control-icon-size} / 2) + #{$plyr-control-padding});
|
||||||
transform: translateX(50%);
|
transform: translateX(50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__controls > .plyr__control:first-child,
|
.plyr__controls > .plyr__control:first-child,
|
||||||
.plyr__controls > .plyr__control:first-child + .plyr__control,
|
.plyr__controls > .plyr__control:first-child + .plyr__control,
|
||||||
.plyr__controls > .plyr__control:last-child {
|
.plyr__controls > .plyr__control:last-child {
|
||||||
&:hover .plyr__tooltip,
|
&:hover .plyr__tooltip,
|
||||||
&.plyr__tab-focus .plyr__tooltip,
|
&.plyr__tab-focus .plyr__tooltip,
|
||||||
.plyr__tooltip--visible {
|
.plyr__tooltip--visible {
|
||||||
transform: translate(0, 0) scale(1);
|
transform: translate(0, 0) scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,23 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__volume {
|
.plyr__volume {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 110px;
|
max-width: 110px;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
margin-left: calc(#{$plyr-control-spacing} / 2);
|
||||||
margin-right: ($plyr-control-spacing / 2);
|
margin-right: calc(#{$plyr-control-spacing} / 2);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto size on iOS as there's no slider
|
// Auto size on iOS as there's no slider
|
||||||
.plyr--is-ios .plyr__volume {
|
.plyr--is-ios .plyr__volume {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-17
@@ -3,29 +3,29 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
@keyframes plyr-progress {
|
@keyframes plyr-progress {
|
||||||
to {
|
to {
|
||||||
background-position: $plyr-progress-loading-size 0;
|
background-position: $plyr-progress-loading-size 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes plyr-popup {
|
@keyframes plyr-popup {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transform: translateY(10px);
|
transform: translateY(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes plyr-fade-in {
|
@keyframes plyr-fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-56
@@ -11,12 +11,12 @@ $css-vars-use-native: false !default;
|
|||||||
// Assigns a variable to the global map
|
// Assigns a variable to the global map
|
||||||
///
|
///
|
||||||
@function css-var-assign($varName: null, $varValue: null) {
|
@function css-var-assign($varName: null, $varValue: null) {
|
||||||
@return map-merge(
|
@return map-merge(
|
||||||
$css-vars,
|
$css-vars,
|
||||||
(
|
(
|
||||||
$varName: $varValue,
|
$varName: $varValue,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -30,33 +30,33 @@ $css-vars-use-native: false !default;
|
|||||||
// background: var(--main-background, green);
|
// background: var(--main-background, green);
|
||||||
///
|
///
|
||||||
@function var($args...) {
|
@function var($args...) {
|
||||||
// CHECK PARAMS
|
// CHECK PARAMS
|
||||||
@if (length($args) ==0) {
|
@if (length($args) ==0) {
|
||||||
@error 'Variable name is expected to be passed to the var() function';
|
@error 'Variable name is expected to be passed to the var() function';
|
||||||
}
|
}
|
||||||
@if (str-length(nth($args, 1)) < 2 or str-slice(nth($args, 1), 0, 2) != '--') {
|
@if (str-length(nth($args, 1)) < 2 or str-slice(nth($args, 1), 0, 2) != '--') {
|
||||||
@error "Variable name is expected to start from '--'";
|
@error "Variable name is expected to start from '--'";
|
||||||
|
}
|
||||||
|
|
||||||
|
// PROCESS
|
||||||
|
$var-name: nth($args, 1);
|
||||||
|
$var-value: map-get($css-vars, $var-name);
|
||||||
|
|
||||||
|
@if ($css-vars-use-native) {
|
||||||
|
// CSS variables
|
||||||
|
// Native CSS: don't process function in case of native
|
||||||
|
@return unquote('var(' + $args + ')');
|
||||||
|
} @else {
|
||||||
|
@if ($var-value == null) {
|
||||||
|
// variable is not provided so far
|
||||||
|
@if (length($args) == 2) {
|
||||||
|
$var-value: nth($args, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PROCESS
|
// Sass: return value from the map
|
||||||
$var-name: nth($args, 1);
|
@return $var-value;
|
||||||
$var-value: map-get($css-vars, $var-name);
|
}
|
||||||
|
|
||||||
@if ($css-vars-use-native) {
|
|
||||||
// CSS variables
|
|
||||||
// Native CSS: don't process function in case of native
|
|
||||||
@return unquote('var(' + $args + ')');
|
|
||||||
} @else {
|
|
||||||
@if ($var-value == null) {
|
|
||||||
// variable is not provided so far
|
|
||||||
@if (length($args) == 2) {
|
|
||||||
$var-value: nth($args, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sass: return value from the map
|
|
||||||
@return $var-value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -69,32 +69,32 @@ $css-vars-use-native: false !default;
|
|||||||
// ));
|
// ));
|
||||||
///
|
///
|
||||||
@mixin css-vars($var-map: null) {
|
@mixin css-vars($var-map: null) {
|
||||||
// CHECK PARAMS
|
// CHECK PARAMS
|
||||||
@if ($var-map == null) {
|
@if ($var-map == null) {
|
||||||
@error 'Map of variables is expected, instead got: null';
|
@error 'Map of variables is expected, instead got: null';
|
||||||
}
|
}
|
||||||
@if (type_of($var-map) != map) {
|
@if (type_of($var-map) != map) {
|
||||||
@error 'Map of variables is expected, instead got another type passed: #{type_of($var, ap)}';
|
@error 'Map of variables is expected, instead got another type passed: #{type_of($var, ap)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
// PROCESS
|
// PROCESS
|
||||||
@if ($css-vars-use-native) {
|
@if ($css-vars-use-native) {
|
||||||
// CSS variables
|
// CSS variables
|
||||||
// Native CSS: assign CSS custom properties to the global scope
|
// Native CSS: assign CSS custom properties to the global scope
|
||||||
@at-root :root {
|
@at-root :root {
|
||||||
@each $var-name, $var-value in $var-map {
|
@each $var-name, $var-value in $var-map {
|
||||||
@if (type_of($var-value) == string) {
|
@if (type_of($var-value) == string) {
|
||||||
#{$var-name}: $var-value; // to prevent quotes interpolation
|
#{$var-name}: $var-value; // to prevent quotes interpolation
|
||||||
} @else {
|
} @else {
|
||||||
#{$var-name}: #{$var-value};
|
#{$var-name}: #{$var-value};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
// Sass or debug
|
|
||||||
// merge variables and values to the global map (provides no output)
|
|
||||||
@each $var-name, $var-value in $var-map {
|
|
||||||
$css-vars: css-var-assign($varName, $varValue) !global; // store in global variable
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} @else {
|
||||||
|
// Sass or debug
|
||||||
|
// merge variables and values to the global map (provides no output)
|
||||||
|
@each $var-name, $var-value in $var-map {
|
||||||
|
$css-vars: css-var-assign($varName, $varValue) !global; // store in global variable
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user