Compare commits
101 Commits
v3.0.0-bet
...
v3.0.7
Author | SHA1 | Date | |
---|---|---|---|
dda8e30b92 | |||
c4e2e24643 | |||
e020a105a3 | |||
2b7fe9a4f9 | |||
951df64b7f | |||
0976afe282 | |||
7b1e4abda7 | |||
0cf75eed3f | |||
d96957d086 | |||
1a032ea498 | |||
5d079da1b8 | |||
9c1bc6ab08 | |||
3d2ba8c009 | |||
e872ce3f77 | |||
b77756da04 | |||
9b23e13ce8 | |||
5eafe9baff | |||
c251c94131 | |||
17041efc71 | |||
05b8e8a6e0 | |||
f998b996fa | |||
958b47c435 | |||
a27248d3b6 | |||
1b1f7be7ff | |||
59d4a27240 | |||
75e9f3c2e3 | |||
7132eccf50 | |||
e953c6398c | |||
bb7eea27e5 | |||
595c5e95bc | |||
43e6dcd41d | |||
b06c8ae43f | |||
c7ea13c0c7 | |||
0f8c6e147b | |||
e566365288 | |||
a06e0f5890 | |||
3bccc0da01 | |||
a0173d991e | |||
600f0eb8a3 | |||
5db73b1327 | |||
5cb1628cd8 | |||
c74b75e8e1 | |||
0538476d6f | |||
5ebe18d081 | |||
207adde36d | |||
1b13ddaa54 | |||
9981c349be | |||
b3365a7373 | |||
9a0c1c830d | |||
ef27ba16f4 | |||
e206edc1f6 | |||
c734bc4957 | |||
572b8a7aca | |||
eebae4a227 | |||
e0562752ea | |||
6a2ca534d2 | |||
7adc2bc6c8 | |||
ba8d7831a7 | |||
69ffcbad27 | |||
819f7d1080 | |||
409b588458 | |||
e90a603d57 | |||
6f061621ad | |||
0300610108 | |||
2fba5f152c | |||
317b08c703 | |||
e6db374a72 | |||
bfb550b8d0 | |||
174234c166 | |||
24b4220de5 | |||
f1895a4cce | |||
c2a6306d46 | |||
7ac732f45b | |||
c90f1bdf08 | |||
6a9be8d16b | |||
58c2c52c95 | |||
73a39769d4 | |||
7221e26eca | |||
98adb8e784 | |||
a59dcb2f53 | |||
d21b58e1c9 | |||
d6e84cbabb | |||
fcccf1d479 | |||
211db12a3d | |||
ab7f277a1b | |||
ce1d5a60d6 | |||
f67315e20c | |||
2150c44036 | |||
70c9fbdde3 | |||
f3ea31c515 | |||
1ee88cba16 | |||
af3ae75229 | |||
d76ef3ff91 | |||
2691c7c9d6 | |||
26b1d8ce8f | |||
6fae148fc1 | |||
bb51647fe2 | |||
71efbe7a92 | |||
afd695cb39 | |||
c4eb4c97ac | |||
d5a1a7ca1c |
@ -8,6 +8,7 @@
|
|||||||
"globals": { "Plyr": false, "jQuery": false },
|
"globals": { "Plyr": false, "jQuery": false },
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-const-assign": 1,
|
"no-const-assign": 1,
|
||||||
|
"no-shadow": 0,
|
||||||
"no-this-before-super": 1,
|
"no-this-before-super": 1,
|
||||||
"no-undef": 1,
|
"no-undef": 1,
|
||||||
"no-unreachable": 1,
|
"no-unreachable": 1,
|
||||||
|
4
.npmignore
Normal file
4
.npmignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
demo
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
*.code-workspace
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
||||||
"extends": ["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": [
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"plyr.css": "src/sass/plyr.scss"
|
"plyr.css": "src/sass/plyr.scss"
|
||||||
},
|
},
|
||||||
"js": {
|
"js": {
|
||||||
"plyr.js": "src/js/plyr.js"
|
"plyr.js": "src/js/plyr.js",
|
||||||
|
"plyr.polyfilled.js": "src/js/plyr.polyfilled.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"demo": {
|
"demo": {
|
||||||
|
667
changelog.md
667
changelog.md
File diff suppressed because it is too large
Load Diff
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
4035
demo/dist/demo.js
vendored
4035
demo/dist/demo.js
vendored
File diff suppressed because it is too large
Load Diff
2
demo/dist/demo.js.map
vendored
2
demo/dist/demo.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.min.js
vendored
Normal file
2
demo/dist/demo.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
demo/dist/demo.min.js.map
vendored
Normal file
1
demo/dist/demo.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -66,7 +66,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Premium video monitization from
|
<p>Premium video monitization from
|
||||||
<a href="https://vi.ai/publisher-video-monetization/" target="_blank" class="no-border">
|
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
||||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
|
<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>
|
||||||
@ -163,25 +163,25 @@
|
|||||||
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>
|
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>
|
||||||
</svg>
|
</svg>
|
||||||
<p>If you think Plyr's good,
|
<p>If you think Plyr's good,
|
||||||
<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"
|
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
||||||
target="_blank" data-shr-network="twitter">tweet it</a>
|
target="_blank" data-shr-network="twitter">tweet it</a>
|
||||||
</p>
|
</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Polyfills -->
|
<!-- Polyfills -->
|
||||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,fetch"></script>
|
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Plyr core script -->
|
<!-- Plyr core script -->
|
||||||
<script src="../dist/plyr.js"></script>
|
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Sharing libary (https://shr.one) -->
|
<!-- Sharing libary (https://shr.one) -->
|
||||||
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
|
<script src="https://cdn.shr.one/1.0.1/shr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
|
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Docs script -->
|
<!-- Docs script -->
|
||||||
<script src="dist/demo.js"></script>
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,183 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
|
||||||
<meta name="description" property="og:description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
|
|
||||||
<meta name="author" content="Sam Potts">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!-- Icons -->
|
|
||||||
<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/16x16.png" sizes="16x16">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
|
|
||||||
|
|
||||||
<!-- Opengraph -->
|
|
||||||
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player">
|
|
||||||
<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 -->
|
|
||||||
<meta name="twitter:card" content="summary">
|
|
||||||
<meta name="twitter:site" content="@sam_potts">
|
|
||||||
<meta name="twitter:creator" content="@sam_potts">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
|
|
||||||
<!-- Docs styles -->
|
|
||||||
<link rel="stylesheet" href="dist/demo.css">
|
|
||||||
|
|
||||||
<!-- Preload -->
|
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="grid">
|
|
||||||
<header>
|
|
||||||
<h1>Plyr</h1>
|
|
||||||
<p>A simple, accessible and customisable media player for
|
|
||||||
<button type="button" class="faux-link" data-source="video">
|
|
||||||
<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>Video</button>,
|
|
||||||
<button type="button" class="faux-link" data-source="audio">
|
|
||||||
<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>Audio</button>,
|
|
||||||
<button type="button" class="faux-link" data-source="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</button> and
|
|
||||||
<button type="button" class="faux-link" data-source="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</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!--<p>Monetization options provided by
|
|
||||||
<a href="https://vi.ai" target="_blank">vi.ai</a>
|
|
||||||
</p>-->
|
|
||||||
|
|
||||||
<div class="call-to-action">
|
|
||||||
<span class="button--with-count">
|
|
||||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button" data-shr-network="github">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>GitHub</title>
|
|
||||||
<path d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
|
||||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
|
||||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
|
||||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
|
||||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
|
||||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"></path>
|
|
||||||
</svg>
|
|
||||||
Download on GitHub
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<video controls crossorigin playsinline poster="media/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
|
||||||
<!-- Video files -->
|
|
||||||
<source src="media/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
|
||||||
<!--<source src="media/View_From_A_Blue_Moon_Trailer-UHD.mp4" type="video/mp4">-->
|
|
||||||
|
|
||||||
<!-- Text track file -->
|
|
||||||
<track kind="captions" label="English" srclang="en" src="media/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
|
|
||||||
<track kind="captions" label="Français" srclang="fr" src="media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
|
||||||
|
|
||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
|
||||||
<a href="media/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
|
||||||
</video>
|
|
||||||
|
|
||||||
<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="http://viewfromabluemoon.com/" 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/ondemand/viewfromabluemoon4k" target="_blank">View From A Blue Moon</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
|
|
||||||
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"></path>
|
|
||||||
</svg>
|
|
||||||
<p>If you think Plyr's good,
|
|
||||||
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
|
||||||
target="_blank" data-shr-network="twitter">tweet it</a>
|
|
||||||
</p>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Polyfills -->
|
|
||||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,fetch"></script>
|
|
||||||
|
|
||||||
<!-- Plyr core script -->
|
|
||||||
<script src="../dist/plyr.js"></script>
|
|
||||||
|
|
||||||
<!-- Sharing libary (https://shr.one) -->
|
|
||||||
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
|
|
||||||
|
|
||||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
|
||||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
|
|
||||||
|
|
||||||
<!-- Docs script -->
|
|
||||||
<script src="dist/demo.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -4,242 +4,276 @@
|
|||||||
// Please see readme.md in the root or github.com/sampotts/plyr
|
// Please see readme.md in the root or github.com/sampotts/plyr
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
import Raven from 'raven-js';
|
||||||
if (window.shr) {
|
|
||||||
window.shr.setup({
|
(() => {
|
||||||
count: {
|
const isLive = window.location.host === 'plyr.io';
|
||||||
classname: 'button__count',
|
|
||||||
},
|
// Raven / Sentry
|
||||||
});
|
// For demo site (https://plyr.io) only
|
||||||
|
if (isLive) {
|
||||||
|
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup tab focus
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const tabClassName = 'tab-focus';
|
Raven.context(() => {
|
||||||
|
if (window.shr) {
|
||||||
|
window.shr.setup({
|
||||||
|
count: {
|
||||||
|
classname: 'button__count',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Remove class on blur
|
// Setup tab focus
|
||||||
document.addEventListener('focusout', event => {
|
const tabClassName = 'tab-focus';
|
||||||
event.target.classList.remove(tabClassName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Remove class on blur
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('focusout', event => {
|
||||||
if (event.keyCode !== 9) {
|
event.target.classList.remove(tabClassName);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
// Add classname to tabbed elements
|
||||||
// This event fires before the focusin event
|
document.addEventListener('keydown', event => {
|
||||||
window.setTimeout(() => {
|
if (event.keyCode !== 9) {
|
||||||
document.activeElement.classList.add(tabClassName);
|
return;
|
||||||
}, 0);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Setup the player
|
// Delay the adding of classname until the focus has changed
|
||||||
const player = new window.Plyr('#player', {
|
// This event fires before the focusin event
|
||||||
debug: true,
|
setTimeout(() => {
|
||||||
title: 'View From A Blue Moon',
|
document.activeElement.classList.add(tabClassName);
|
||||||
iconUrl: '../dist/plyr.svg',
|
}, 0);
|
||||||
keyboard: {
|
});
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
controls: true,
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
keys: {
|
|
||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
|
||||||
},
|
|
||||||
ads: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose for testing
|
// Setup the player
|
||||||
window.player = player;
|
const player = new Plyr('#player', {
|
||||||
|
debug: true,
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
iconUrl: '../dist/plyr.svg',
|
||||||
|
keyboard: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
controls: true,
|
||||||
|
},
|
||||||
|
/* controls: [
|
||||||
|
'play-large',
|
||||||
|
'restart',
|
||||||
|
'rewind',
|
||||||
|
'play',
|
||||||
|
'fast-forward',
|
||||||
|
'progress',
|
||||||
|
'current-time',
|
||||||
|
'mute',
|
||||||
|
'volume',
|
||||||
|
'captions',
|
||||||
|
'settings',
|
||||||
|
'pip',
|
||||||
|
'airplay',
|
||||||
|
'fullscreen',
|
||||||
|
], */
|
||||||
|
captions: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||||
|
},
|
||||||
|
ads: {
|
||||||
|
enabled: true,
|
||||||
|
publisherId: '918848828995742',
|
||||||
|
},
|
||||||
|
/* listeners: {
|
||||||
|
seek: () => false,
|
||||||
|
}, */
|
||||||
|
});
|
||||||
|
|
||||||
// Setup type toggle
|
// Expose for tinkering in the console
|
||||||
const buttons = document.querySelectorAll('[data-source]');
|
window.player = player;
|
||||||
const types = {
|
|
||||||
video: 'video',
|
|
||||||
audio: 'audio',
|
|
||||||
youtube: 'youtube',
|
|
||||||
vimeo: 'vimeo',
|
|
||||||
};
|
|
||||||
let currentType = window.location.hash.replace('#', '');
|
|
||||||
const historySupport = window.history && window.history.pushState;
|
|
||||||
|
|
||||||
// Toggle class on an element
|
// Setup type toggle
|
||||||
function toggleClass(element, className, state) {
|
const buttons = document.querySelectorAll('[data-source]');
|
||||||
if (element) {
|
const types = {
|
||||||
element.classList[state ? 'add' : 'remove'](className);
|
video: 'video',
|
||||||
}
|
audio: 'audio',
|
||||||
}
|
youtube: 'youtube',
|
||||||
|
vimeo: 'vimeo',
|
||||||
|
};
|
||||||
|
let currentType = window.location.hash.replace('#', '');
|
||||||
|
const historySupport = window.history && window.history.pushState;
|
||||||
|
|
||||||
// Set a new source
|
// Toggle class on an element
|
||||||
function newSource(type, init) {
|
function toggleClass(element, className, state) {
|
||||||
// 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 (element) {
|
||||||
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
|
element.classList[state ? 'add' : 'remove'](className);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
// Set a new source
|
||||||
case types.video:
|
function newSource(type, init) {
|
||||||
player.source = {
|
// 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
|
||||||
type: 'video',
|
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
|
||||||
title: 'View From A Blue Moon',
|
return;
|
||||||
sources: [{
|
}
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
}],
|
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
|
||||||
tracks: [
|
|
||||||
{
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'English',
|
|
||||||
srclang: 'en',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'French',
|
|
||||||
srclang: 'fr',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
switch (type) {
|
||||||
|
case types.video:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
}],
|
||||||
|
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
|
tracks: [
|
||||||
|
{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'English',
|
||||||
|
srclang: 'en',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'French',
|
||||||
|
srclang: 'fr',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
case types.audio:
|
break;
|
||||||
player.source = {
|
|
||||||
type: 'audio',
|
|
||||||
title: 'Kishi Bashi – “It All Began With A Burst”',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
|
||||||
type: 'audio/mp3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
|
||||||
type: 'audio/ogg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
case types.audio:
|
||||||
|
player.source = {
|
||||||
|
type: 'audio',
|
||||||
|
title: 'Kishi Bashi – “It All Began With A Burst”',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
||||||
|
type: 'audio/mp3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
||||||
|
type: 'audio/ogg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
case types.youtube:
|
break;
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
|
||||||
provider: 'youtube',
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
case types.youtube:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
|
provider: 'youtube',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
case types.vimeo:
|
break;
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://vimeo.com/76979871',
|
|
||||||
provider: 'vimeo',
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
case types.vimeo:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://vimeo.com/25345658',
|
||||||
|
provider: 'vimeo',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the current type for next time
|
default:
|
||||||
currentType = type;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove active classes
|
// Set the current type for next time
|
||||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
currentType = type;
|
||||||
|
|
||||||
// Set active on parent
|
// Remove active classes
|
||||||
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
// Show cite
|
// Set active on parent
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
||||||
cite.setAttribute('hidden', '');
|
|
||||||
});
|
|
||||||
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to each button
|
// Show cite
|
||||||
Array.from(buttons).forEach(button => {
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
||||||
button.addEventListener('click', () => {
|
cite.setAttribute('hidden', '');
|
||||||
const type = button.getAttribute('data-source');
|
});
|
||||||
|
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
newSource(type);
|
// Bind to each button
|
||||||
|
Array.from(buttons).forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const type = button.getAttribute('data-source');
|
||||||
|
|
||||||
|
newSource(type);
|
||||||
|
|
||||||
|
if (historySupport) {
|
||||||
|
window.history.pushState({ type }, '', `#${type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// List for backwards/forwards
|
||||||
|
window.addEventListener('popstate', event => {
|
||||||
|
if (event.state && 'type' in event.state) {
|
||||||
|
newSource(event.state.type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// On load
|
||||||
if (historySupport) {
|
if (historySupport) {
|
||||||
window.history.pushState({ type }, '', `#${type}`);
|
const video = !currentType.length;
|
||||||
|
|
||||||
|
// If there's no current type set, assume video
|
||||||
|
if (video) {
|
||||||
|
currentType = types.video;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace current history state
|
||||||
|
if (currentType in types) {
|
||||||
|
window.history.replaceState(
|
||||||
|
{
|
||||||
|
type: currentType,
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
video ? '' : `#${currentType}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not video, load the source
|
||||||
|
if (currentType !== types.video) {
|
||||||
|
newSource(currentType, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// List for backwards/forwards
|
// Google analytics
|
||||||
window.addEventListener('popstate', event => {
|
// For demo site (https://plyr.io) only
|
||||||
if (event.state && 'type' in event.state) {
|
/* eslint-disable */
|
||||||
newSource(event.state.type);
|
if (isLive) {
|
||||||
}
|
(function(i, s, o, g, r, a, m) {
|
||||||
});
|
i.GoogleAnalyticsObject = r;
|
||||||
|
i[r] =
|
||||||
// On load
|
i[r] ||
|
||||||
if (historySupport) {
|
function() {
|
||||||
const video = !currentType.length;
|
(i[r].q = i[r].q || []).push(arguments);
|
||||||
|
};
|
||||||
// If there's no current type set, assume video
|
i[r].l = 1 * new Date();
|
||||||
if (video) {
|
a = s.createElement(o);
|
||||||
currentType = types.video;
|
m = s.getElementsByTagName(o)[0];
|
||||||
}
|
a.async = 1;
|
||||||
|
a.src = g;
|
||||||
// Replace current history state
|
m.parentNode.insertBefore(a, m);
|
||||||
if (currentType in types) {
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
window.history.replaceState(
|
window.ga('create', 'UA-40881672-11', 'auto');
|
||||||
{
|
window.ga('send', 'pageview');
|
||||||
type: currentType,
|
|
||||||
},
|
|
||||||
'',
|
|
||||||
video ? '' : `#${currentType}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not video, load the source
|
|
||||||
if (currentType !== types.video) {
|
|
||||||
newSource(currentType, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
/* eslint-enable */
|
||||||
|
})();
|
||||||
// Google analytics
|
|
||||||
// For demo site (https://plyr.io) only
|
|
||||||
/* eslint-disable */
|
|
||||||
if (window.location.host === 'plyr.io') {
|
|
||||||
(function(i, s, o, g, r, a, m) {
|
|
||||||
i.GoogleAnalyticsObject = r;
|
|
||||||
i[r] =
|
|
||||||
i[r] ||
|
|
||||||
function() {
|
|
||||||
(i[r].q = i[r].q || []).push(arguments);
|
|
||||||
};
|
|
||||||
i[r].l = 1 * new Date();
|
|
||||||
a = s.createElement(o);
|
|
||||||
m = s.getElementsByTagName(o)[0];
|
|
||||||
a.async = 1;
|
|
||||||
a.src = g;
|
|
||||||
m.parentNode.insertBefore(a, m);
|
|
||||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
|
||||||
window.ga('create', 'UA-40881672-11', 'auto');
|
|
||||||
window.ga('send', 'pageview');
|
|
||||||
}
|
|
||||||
/* eslint-enable */
|
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
.button,
|
.button,
|
||||||
.button__count {
|
.button__count {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: $color-button-background;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||||
color: $gray;
|
color: $color-button-text;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: ($spacing-base * 0.75);
|
padding: ($spacing-base * 0.75);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -71,7 +71,7 @@
|
|||||||
&::before {
|
&::before {
|
||||||
border: $arrow-size solid transparent;
|
border: $arrow-size solid transparent;
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-right-color: #fff;
|
border-right-color: $color-button-background;
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -28,6 +28,7 @@ body {
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
padding-bottom: 1px; // Collapsing margins
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,5 +24,9 @@ $color-vimeo: #19b7ed;
|
|||||||
$color-link: #fff;
|
$color-link: #fff;
|
||||||
$color-background: $color-brand-primary;
|
$color-background: $color-brand-primary;
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
$color-button-background: #fff;
|
||||||
|
$color-button-text: $gray;
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
$tab-focus-default-color: #fff;
|
$tab-focus-default-color: #fff;
|
||||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
7754
dist/plyr.js
vendored
7754
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js
vendored
Normal file
2
dist/plyr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/plyr.min.js.map
vendored
Normal file
1
dist/plyr.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12943
dist/plyr.polyfilled.js
vendored
Normal file
12943
dist/plyr.polyfilled.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/plyr.polyfilled.js.map
vendored
Normal file
1
dist/plyr.polyfilled.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
Normal file
2
dist/plyr.polyfilled.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/plyr.polyfilled.min.js.map
vendored
Normal file
1
dist/plyr.polyfilled.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
88
gulpfile.js
88
gulpfile.js
@ -9,6 +9,7 @@ const path = require('path');
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const gutil = require('gulp-util');
|
const gutil = require('gulp-util');
|
||||||
const concat = require('gulp-concat');
|
const concat = require('gulp-concat');
|
||||||
|
const filter = require('gulp-filter');
|
||||||
const sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
const cleancss = require('gulp-clean-css');
|
const cleancss = require('gulp-clean-css');
|
||||||
const run = require('run-sequence');
|
const run = require('run-sequence');
|
||||||
@ -24,8 +25,7 @@ const size = require('gulp-size');
|
|||||||
const rollup = require('gulp-better-rollup');
|
const rollup = require('gulp-better-rollup');
|
||||||
const babel = require('rollup-plugin-babel');
|
const babel = require('rollup-plugin-babel');
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
const uglify = require('rollup-plugin-uglify');
|
const uglify = require('gulp-uglify-es').default;
|
||||||
const { minify } = require('uglify-es');
|
|
||||||
const commonjs = require('rollup-plugin-commonjs');
|
const commonjs = require('rollup-plugin-commonjs');
|
||||||
const resolve = require('rollup-plugin-node-resolve');
|
const resolve = require('rollup-plugin-node-resolve');
|
||||||
|
|
||||||
@ -40,6 +40,8 @@ try {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minSuffix = '.min';
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
const root = __dirname;
|
const root = __dirname;
|
||||||
const paths = {
|
const paths = {
|
||||||
@ -68,8 +70,11 @@ const paths = {
|
|||||||
root: path.join(root, 'demo/'),
|
root: path.join(root, 'demo/'),
|
||||||
},
|
},
|
||||||
upload: [
|
upload: [
|
||||||
path.join(root, 'dist/**'),
|
path.join(root, `dist/*${minSuffix}.*`),
|
||||||
path.join(root, 'demo/dist/**'),
|
path.join(root, 'dist/*.css'),
|
||||||
|
path.join(root, 'dist/*.svg'),
|
||||||
|
path.join(root, `demo/dist/*${minSuffix}.*`),
|
||||||
|
path.join(root, 'demo/dist/*.css'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,6 +127,7 @@ const build = {
|
|||||||
Object.keys(files).forEach(key => {
|
Object.keys(files).forEach(key => {
|
||||||
const name = `js:${key}`;
|
const name = `js:${key}`;
|
||||||
tasks.js.push(name);
|
tasks.js.push(name);
|
||||||
|
const { output } = paths[bundle];
|
||||||
|
|
||||||
gulp.task(name, () =>
|
gulp.task(name, () =>
|
||||||
gulp
|
gulp
|
||||||
@ -135,15 +141,19 @@ const build = {
|
|||||||
resolve(),
|
resolve(),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
babel(babelrc),
|
babel(babelrc),
|
||||||
uglify({}, minify),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(sourcemaps.write(''))
|
.pipe(sourcemaps.write(''))
|
||||||
.pipe(gulp.dest(paths[bundle].output)),
|
.pipe(gulp.dest(output))
|
||||||
|
.pipe(filter('**/*.js'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(size(sizeOptions))
|
||||||
|
.pipe(rename({ suffix: minSuffix }))
|
||||||
|
.pipe(sourcemaps.write(''))
|
||||||
|
.pipe(gulp.dest(output)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -266,6 +276,23 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
const semver = new RegExp(`v${regex}`, 'gi');
|
const semver = new RegExp(`v${regex}`, 'gi');
|
||||||
const localPath = new RegExp('(../)?dist', 'gi');
|
const localPath = new RegExp('(../)?dist', 'gi');
|
||||||
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
||||||
|
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
||||||
|
|
||||||
|
gulp.task('version', () => {
|
||||||
|
console.log(`Updating versions to '${version}'...`);
|
||||||
|
|
||||||
|
// Replace versioned URLs in source
|
||||||
|
const files = [
|
||||||
|
'plyr.js',
|
||||||
|
'plyr.polyfilled.js',
|
||||||
|
'defaults.js',
|
||||||
|
];
|
||||||
|
gulp
|
||||||
|
.src(files.map(file => path.join(root, `src/js/${file}`)))
|
||||||
|
.pipe(replace(semver, `v${version}`))
|
||||||
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
|
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
||||||
|
});
|
||||||
|
|
||||||
// Publish version to CDN bucket
|
// Publish version to CDN bucket
|
||||||
gulp.task('cdn', () => {
|
gulp.task('cdn', () => {
|
||||||
@ -277,22 +304,26 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
|
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
|
||||||
|
|
||||||
// Upload to CDN
|
// Upload to CDN
|
||||||
return gulp
|
return (
|
||||||
.src(paths.upload)
|
gulp
|
||||||
.pipe(
|
.src(paths.upload)
|
||||||
size({
|
.pipe(
|
||||||
showFiles: true,
|
rename(p => {
|
||||||
gzip: true,
|
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||||
}),
|
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||||
)
|
}),
|
||||||
.pipe(
|
)
|
||||||
rename(p => {
|
// Remove min suffix from source map URL
|
||||||
// eslint-disable-next-line
|
.pipe(replace(/sourceMappingURL=([\w-?.]+)/, (match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`))
|
||||||
p.dirname = p.dirname.replace('.', version);
|
.pipe(
|
||||||
}),
|
size({
|
||||||
)
|
showFiles: true,
|
||||||
.pipe(replace(localPath, versionPath))
|
gzip: true,
|
||||||
.pipe(s3(aws.cdn, options.cdn));
|
}),
|
||||||
|
)
|
||||||
|
.pipe(replace(localPath, versionPath))
|
||||||
|
.pipe(s3(aws.cdn, options.cdn))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish to demo bucket
|
// Publish to demo bucket
|
||||||
@ -304,21 +335,12 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
|
|
||||||
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
||||||
|
|
||||||
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
|
||||||
|
|
||||||
// Replace versioned files in readme.md
|
// Replace versioned files in readme.md
|
||||||
gulp
|
gulp
|
||||||
.src([`${root}/readme.md`])
|
.src([`${root}/readme.md`])
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
.pipe(gulp.dest(root));
|
.pipe(gulp.dest(root));
|
||||||
|
|
||||||
// Replace versioned files in plyr.js
|
|
||||||
gulp
|
|
||||||
.src(path.join(root, 'src/js/plyr.js'))
|
|
||||||
.pipe(replace(semver, `v${version}`))
|
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
|
||||||
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
|
||||||
|
|
||||||
// Replace local file paths with remote paths in demo HTML
|
// Replace local file paths with remote paths in demo HTML
|
||||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||||
const index = `${paths.demo.root}index.html`;
|
const index = `${paths.demo.root}index.html`;
|
||||||
@ -385,6 +407,6 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
|
|
||||||
// Do everything
|
// Do everything
|
||||||
gulp.task('publish', () => {
|
gulp.task('publish', () => {
|
||||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
|
run('version', tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
52
package.json
52
package.json
@ -1,50 +1,53 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.0.0-beta.8",
|
"version": "3.0.7",
|
||||||
"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",
|
||||||
"main": "./dist",
|
"main": "./dist/plyr.js",
|
||||||
|
"browser": "./dist/plyr.min.js",
|
||||||
"sass": "./src/sass/plyr.scss",
|
"sass": "./src/sass/plyr.scss",
|
||||||
"style": "./dist/plyr.css",
|
"style": "./dist/plyr.css",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.2.1",
|
"babel-eslint": "^8.2.2",
|
||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"babel-plugin-external-helpers": "^6.22.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"eslint": "^4.16.0",
|
"eslint": "^4.19.0",
|
||||||
"eslint-config-airbnb-base": "^12.1.0",
|
"eslint-config-airbnb-base": "^12.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-import": "^2.9.0",
|
||||||
"git-branch": "^1.0.0",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^4.1.0",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
"gulp-better-rollup": "^3.0.0",
|
"gulp-better-rollup": "^3.0.0",
|
||||||
"gulp-clean-css": "^3.9.2",
|
"gulp-clean-css": "^3.9.3",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-open": "^2.1.0",
|
"gulp-filter": "^5.1.0",
|
||||||
|
"gulp-open": "^3.0.0",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-replace": "^0.6.1",
|
"gulp-replace": "^0.6.1",
|
||||||
"gulp-s3": "^0.11.0",
|
"gulp-s3": "^0.11.0",
|
||||||
"gulp-sass": "^3.1.0",
|
"gulp-sass": "^3.1.0",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-sourcemaps": "^1.12.1",
|
"gulp-sourcemaps": "^2.6.4",
|
||||||
"gulp-svgmin": "^1.2.4",
|
"gulp-svgmin": "^1.2.4",
|
||||||
"gulp-svgstore": "^6.1.1",
|
"gulp-svgstore": "^6.1.1",
|
||||||
|
"gulp-uglify-es": "^1.0.1",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
|
"prettier-eslint": "^8.8.1",
|
||||||
|
"prettier-stylelint": "^0.4.2",
|
||||||
"rollup-plugin-babel": "^3.0.3",
|
"rollup-plugin-babel": "^3.0.3",
|
||||||
"rollup-plugin-commonjs": "^8.2.6",
|
"rollup-plugin-commonjs": "^8.4.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.2",
|
"rollup-plugin-node-resolve": "^3.2.0",
|
||||||
"rollup-plugin-uglify": "^3.0.0",
|
|
||||||
"run-sequence": "^2.2.1",
|
"run-sequence": "^2.2.1",
|
||||||
"stylelint": "^8.4.0",
|
"stylelint": "^9.1.3",
|
||||||
"stylelint-config-prettier": "^2.0.0",
|
"stylelint-config-prettier": "^3.0.4",
|
||||||
"stylelint-config-sass-guidelines": "^4.1.0",
|
"stylelint-config-recommended": "^2.1.0",
|
||||||
"stylelint-config-standard": "^18.0.0",
|
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||||
"stylelint-order": "^0.8.0",
|
"stylelint-order": "^0.8.1",
|
||||||
"stylelint-scss": "^2.2.0",
|
"stylelint-scss": "^2.5.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
"stylelint-selector-bem-pattern": "^2.0.0"
|
||||||
"uglify-es": "^3.3.8"
|
|
||||||
},
|
},
|
||||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -62,5 +65,10 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"dependencies": {}
|
"dependencies": {
|
||||||
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"custom-event-polyfill": "^0.3.0",
|
||||||
|
"loadjs": "^3.5.2",
|
||||||
|
"raven-js": "^3.23.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
414
readme.md
414
readme.md
@ -1,33 +1,33 @@
|
|||||||
---
|
|
||||||
Beware: This version is currently in beta and not production-ready
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plyr
|
# Plyr
|
||||||
|
|
||||||
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
||||||
|
|
||||||
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](http://bit.ly/plyr-chat)
|
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-chat)
|
||||||
|
|
||||||
[](https://plyr.io)
|
[](https://plyr.io)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Accessible** - full support for VTT captions and screen readers
|
* **Accessible** - full support for VTT captions and screen readers
|
||||||
* **Lightweight** - just 18KB minified and gzipped
|
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||||
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
* **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||||
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
`<span>` or `<a href="#">` button hacks
|
||||||
`<span>` or `<a href="#">` button hacks
|
* **Responsive** - works with any screen size
|
||||||
* **Responsive** - works with any screen size
|
* **HTML Video & Audio** - support for both formats
|
||||||
* **HTML Video & Audio** - support for both formats
|
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||||
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
* **[Monetization](#ads)** - make money from your videos
|
||||||
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
||||||
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||||
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||||
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
||||||
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
||||||
* **i18n support** - support for internationalization of controls
|
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
|
||||||
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
* **Playsinline** - supports the `playsinline` attribute
|
||||||
* **SASS** - to include in your build processes
|
* **Speed controls** - adjust speed on the fly
|
||||||
|
* **Multiple captions** - support for multiple caption tracks
|
||||||
|
* **i18n support** - support for internationalization of controls
|
||||||
|
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||||
|
* **SASS** - to include in your build processes
|
||||||
|
|
||||||
Oh and yes, it works with Bootstrap.
|
Oh and yes, it works with Bootstrap.
|
||||||
|
|
||||||
@ -35,40 +35,25 @@ Oh and yes, it works with Bootstrap.
|
|||||||
|
|
||||||
Check out the [changelog](changelog.md) to see what's new with Plyr.
|
Check out the [changelog](changelog.md) to see what's new with Plyr.
|
||||||
|
|
||||||
## CMS plugins
|
## Plugins & Components
|
||||||
|
|
||||||
### [WordPress](https://wordpress.org/plugins/plyr/)
|
Some awesome folks have made plugins for CMSs and Components for JavaScript frameworks:
|
||||||
|
|
||||||
Created and maintained by Ryan Anthony Drake ([@iamryandrake](https://github.com/iamryandrake))
|
| Type | Maintainer | Link |
|
||||||
|
| --------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||||
### [Neos](https://packagist.org/packages/jonnitto/plyr)
|
| WordPress | Ryan Anthony Drake ([@iamryandrake](https://github.com/iamryandrake)) | [https://wordpress.org/plugins/plyr/](https://wordpress.org/plugins/plyr/) |
|
||||||
|
| React | Jose Miguel Bejarano ([@xDae](https://github.com/xDae)) | [https://github.com/xDae/react-plyr](https://github.com/xDae/react-plyr) |
|
||||||
Created and maintained by Jon Uhlmann ([@jonnitto](https://github.com/jonnitto))
|
| Vue | Gabe Dunn ([@redxtech](https://github.com/redxtech)) | [https://github.com/redxtech/vue-plyr](https://github.com/redxtech/vue-plyr) |
|
||||||
|
| Neos | Jon Uhlmann ([@jonnitto](https://github.com/jonnitto)) | [https://packagist.org/packages/jonnitto/plyr](https://packagist.org/packages/jonnitto/plyr) |
|
||||||
### [Kirby](https://github.com/dpschen/kirby-plyrtag)
|
| Kirby | Dominik Pschenitschni ([@dpschen](https://github.com/dpschen)) | [https://github.com/dpschen/kirby-plyrtag](https://github.com/dpschen/kirby-plyrtag) |
|
||||||
|
|
||||||
Created and maintained by Dominik Pschenitschni ([@dpschen](https://github.com/dpschen))
|
|
||||||
|
|
||||||
## Using package managers
|
|
||||||
|
|
||||||
You can grab the source using one of the following package managers.
|
|
||||||
|
|
||||||
### npm
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install plyr
|
|
||||||
```
|
|
||||||
|
|
||||||
[https://www.npmjs.com/package/plyr](https://www.npmjs.com/package/plyr)
|
|
||||||
|
|
||||||
## Quick setup
|
## Quick setup
|
||||||
|
|
||||||
Here's a quick run through on getting up and running. There's also a [demo on Codepen](http://codepen.io/sampotts/pen/jARJYp).
|
Here's a quick run through on getting up and running. There's also a [demo on Codepen](http://codepen.io/sampotts/pen/jARJYp). You can grab all of the source with [NPM](https://www.npmjs.com/package/plyr) using `npm install plyr`.
|
||||||
|
|
||||||
### HTML
|
### HTML
|
||||||
|
|
||||||
Plyr extends upon the standard HTML5 markup so that's all you need for those types. More info on advanced HTML markup can be found under
|
Plyr extends upon the standard [HTML5 media element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) markup so that's all you need for those types.
|
||||||
[initialising](#initialising).
|
|
||||||
|
|
||||||
#### HTML5 Video
|
#### HTML5 Video
|
||||||
|
|
||||||
@ -95,36 +80,59 @@ For YouTube and Vimeo players, Plyr uses progressive enhancement to enhance the
|
|||||||
|
|
||||||
#### YouTube embed
|
#### YouTube embed
|
||||||
|
|
||||||
|
We recommend [progressive enhancement](https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/) with the embedded players. You can elect to use an `<iframe>` as the source element (which Plyr will progressively enhance) or a bog standard `<div>` with two essential data attributes - `data-plyr-provider` and `data-plyr-embed-id`.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="plyr__video-embed" id="player">
|
<div class="plyr__video-embed" id="player">
|
||||||
<iframe src="https://www.youtube.com/embed/bTqVqk7FSmY?origin=https://plyr.io&iv_load_policy=3&modestbranding=1&playsinline=1&showinfo=0&rel=0&enablejsapi=1" allowfullscreen allowtransparency allow="autoplay"></iframe>
|
<iframe src="https://www.youtube.com/embed/bTqVqk7FSmY?origin=https://plyr.io&iv_load_policy=3&modestbranding=1&playsinline=1&showinfo=0&rel=0&enablejsapi=1" allowfullscreen allowtransparency allow="autoplay"></iframe>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_Note_: The `plyr__video-embed` classname will make the player a responsive 16:9 (most common) iframe embed. When plyr itself kicks in, your custom `ratio` config option will be used.
|
||||||
|
|
||||||
|
Or the `<div>` non progressively enhanced method:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="player" data-plyr-provider="youtube" data-plyr-embed-id="bTqVqk7FSmY"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
_Note_: The `data-plyr-embed-id` can either be the video ID or URL for the media.
|
||||||
|
|
||||||
#### Vimeo embed
|
#### Vimeo embed
|
||||||
|
|
||||||
|
Much the same as YouTube above.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="plyr__video-embed" id="player">
|
<div class="plyr__video-embed" id="player">
|
||||||
<iframe src="https://player.vimeo.com/video/76979871?loop=false&byline=false&portrait=false&title=false&speed=true&transparent=0&gesture=media" allowfullscreen allowtransparency allow="autoplay"></iframe>
|
<iframe src="https://player.vimeo.com/video/76979871?loop=false&byline=false&portrait=false&title=false&speed=true&transparent=0&gesture=media" allowfullscreen allowtransparency allow="autoplay"></iframe>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or the `<div>` non progressively enhanced method:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="player" data-plyr-provider="vimeo" data-plyr-embed-id="76979871"></div>
|
||||||
|
```
|
||||||
|
|
||||||
### JavaScript
|
### JavaScript
|
||||||
|
|
||||||
Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can be found under
|
Include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
||||||
[initialising](#initialising).
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="path/to/plyr.js"></script>
|
<script src="path/to/plyr.js"></script>
|
||||||
<script>const player = new Plyr('#player');</script>
|
<script>const player = new Plyr('#player');</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [initialising](#initialising) for more information on advanced setups.
|
||||||
|
|
||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.0.0-beta.8/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.0.7/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
||||||
|
|
||||||
### CSS
|
### CSS
|
||||||
|
|
||||||
Include the `plyr.css` stylsheet into your `<head>`
|
Include the `plyr.css` stylsheet into your `<head>`
|
||||||
@ -136,13 +144,23 @@ Include the `plyr.css` stylsheet into your `<head>`
|
|||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.0-beta.8/plyr.css">
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.7/plyr.css">
|
||||||
```
|
```
|
||||||
|
|
||||||
### SVG Sprite
|
### SVG Sprite
|
||||||
|
|
||||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.8/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.7/plyr.svg`.
|
||||||
|
|
||||||
|
## Ads
|
||||||
|
|
||||||
|
Plyr has partnered up with [vi.ai](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||||
|
|
||||||
|
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||||
|
* Grab your publisher ID from the code snippet
|
||||||
|
* Enable ads in the [config options](#options) and enter your publisher ID
|
||||||
|
|
||||||
|
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
|
|
||||||
@ -187,11 +205,12 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
|||||||
|
|
||||||
You can specify a range of arguments for the constructor to use:
|
You can specify a range of arguments for the constructor to use:
|
||||||
|
|
||||||
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
||||||
* A [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
* A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||||
* A [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) or Array of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) -
|
* A [`NodeList]`(https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
|
||||||
the first element will be used
|
* A [jQuery](https://jquery.com) object
|
||||||
* A [jQuery](https://jquery.com) object - if multiple are passed, the first element will be used
|
|
||||||
|
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup.
|
||||||
|
|
||||||
Here's some examples
|
Here's some examples
|
||||||
|
|
||||||
@ -215,7 +234,13 @@ const player = new Plyr(document.querySelectorAll('.js-player'));
|
|||||||
|
|
||||||
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>`, or `<div>` wrapper for embeds
|
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>`, or `<div>` wrapper for embeds
|
||||||
|
|
||||||
The second argument for the constructor is the [#options](options) object:
|
##### Setting up multiple players
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const players = Array.from(document.querySelectorAll('.js-player')).map(player => new Plyr(player));
|
||||||
|
```
|
||||||
|
|
||||||
|
The second argument for the constructor is the [options](#options) object:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
@ -235,42 +260,43 @@ Options can be passed as an object to the constructor as above or as JSON in `da
|
|||||||
|
|
||||||
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
|
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
||||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||||
| `controls` | Function or Array | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return a string of HTML for the controls. Three arguments will be passed to your function; id (the unique id for the player), seektime (the seektime step in seconds), and title (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
||||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
||||||
| `i18n` | Object | See [defaults.js](/src/js/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
| `i18n` | Object | See [defaults.js](/src/js/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||||
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
||||||
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
||||||
| `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
| `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||||
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
||||||
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
||||||
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
||||||
| `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
| `clickToPlay` | Boolean | `true` | Click (or tap) of the video container will toggle play/pause. |
|
| `clickToPlay` | Boolean | `true` | Click (or tap) of the video container will toggle play/pause. |
|
||||||
| `disableContextMenu` | Boolean | `true` | Disable right click menu on video to <em>help</em> as very primitive obfuscation to prevent downloads of content. |
|
| `disableContextMenu` | Boolean | `true` | Disable right click menu on video to <em>help</em> as very primitive obfuscation to prevent downloads of content. |
|
||||||
| `hideControls` | Boolean | `true` | Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen. As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly. |
|
| `hideControls` | Boolean | `true` | Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen. As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly. |
|
||||||
| `showPosterOnEnd` | Boolean | false | This will restore and _reload_ HTML5 video once playback is complete. Note: depending on the browser caching, this may result in the video downloading again (or parts of it). Use with caution. |
|
| `showPosterOnEnd` | Boolean | false | This will restore and _reload_ HTML5 video once playback is complete. Note: depending on the browser caching, this may result in the video downloading again (or parts of it). Use with caution. |
|
||||||
| `keyboard` | Object | `{ focused: true, global: false }` | Enable [keyboard shortcuts](#shortcuts) for focused players only or globally |
|
| `keyboard` | Object | `{ focused: true, global: false }` | Enable [keyboard shortcuts](#shortcuts) for focused players only or globally |
|
||||||
| `tooltips` | Object | `{ controls: false, seek: true }` | `controls`: Display control labels as tooltips on `:hover` & `:focus` (by default, the labels are screen reader only). `seek`: Display a seek tooltip to indicate on click where the media would seek to. |
|
| `tooltips` | Object | `{ controls: false, seek: true }` | `controls`: Display control labels as tooltips on `:hover` & `:focus` (by default, the labels are screen reader only). `seek`: Display a seek tooltip to indicate on click where the media would seek to. |
|
||||||
| `duration` | Number | `null` | Specify a custom duration for media. |
|
| `duration` | Number | `null` | Specify a custom duration for media. |
|
||||||
| `displayDuration` | Boolean | `true` | Displays the duration of the media on the "metadataloaded" event (on startup) in the current time display. This will only work if the `preload` attribute is not set to `none` (or is not set at all) and you choose not to display the duration (see `controls` option). |
|
| `displayDuration` | Boolean | `true` | Displays the duration of the media on the "metadataloaded" event (on startup) in the current time display. This will only work if the `preload` attribute is not set to `none` (or is not set at all) and you choose not to display the duration (see `controls` option). |
|
||||||
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
||||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||||
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
|
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||||
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
||||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||||
|
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||||
|
|
||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
@ -296,72 +322,81 @@ element.addEventListener('ready', event => {
|
|||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
Methods are not chainable. An example use of a method:
|
Example method use:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.play();
|
player.play(); // Start playback
|
||||||
|
player.fullscreen.enter(); // Enter fullscreen
|
||||||
```
|
```
|
||||||
|
|
||||||
| Method | Parameters | Description |
|
| Method | Parameters | Description |
|
||||||
| ------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
| `play()`¹ | - | Start playback. |
|
| `play()`¹ | - | Start playback. |
|
||||||
| `pause()` | - | Pause playback. |
|
| `pause()` | - | Pause playback. |
|
||||||
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
||||||
| `stop()` | - | Stop playback and reset to start. |
|
| `stop()` | - | Stop playback and reset to start. |
|
||||||
| `restart()` | - | Restart playback. |
|
| `restart()` | - | Restart playback. |
|
||||||
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||||
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||||
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||||
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||||
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
||||||
| `toggleFullscreen(event)` | Event | Toggle fullscreen. Fullscreen can only be initiated by a user event. Exit is possible without user input. |
|
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
|
||||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||||
| `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. |
|
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
| `toggleControls(toggle)` | Boolean | Toggle the controls based on the specified boolean. |
|
||||||
| `supports(type)` | String | Check support for a mime type. |
|
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||||
|
| `supports(type)` | String | Check support for a mime type. |
|
||||||
|
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||||
|
|
||||||
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
||||||
|
|
||||||
### Getters and Setters
|
### Getters and Setters
|
||||||
|
|
||||||
An example setter:
|
Example setters:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.volume = 0.5;
|
player.volume = 0.5; // Sets volume at 50%
|
||||||
|
player.currentTime = 10; // Seeks to 10 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
An example getter:
|
Example getters:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.volume; // returns 0.5;
|
player.volume; // 0.5;
|
||||||
|
player.currentTime; // 10
|
||||||
|
player.fullscreen.active; // false;
|
||||||
```
|
```
|
||||||
|
|
||||||
| Property | Getter | Setter | Description |
|
| Property | Getter | Setter | Description |
|
||||||
| --------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `isHTML5` | ✔ | - | Returns a boolean indicating if the current player is HTML5. |
|
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
|
||||||
| `isEmbed` | ✔ | - | Returns a boolean indicating if the current player is an embedded player. |
|
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
|
||||||
| `paused` | ✔ | - | Returns a boolean indicating if the current player is paused. |
|
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||||
| `playing` | ✔ | - | Returns a boolean indicating if the current player is playing. |
|
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||||
| `ended` | ✔ | - | Returns a boolean indicating if the current player has finished playback. |
|
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||||
| `currentTime` | ✔ | ✔ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||||
| `seeking` | ✔ | - | Returns a boolean indicating if the current player is seeking. |
|
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||||
| `duration` | ✔ | - | Returns the duration for the current media. |
|
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||||
| `volume` | ✔ | ✔ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||||
| `muted` | ✔ | ✔ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
||||||
| `hasAudio` | ✔ | - | Returns a boolean indicating if the current media has an audio track. |
|
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
||||||
| `speed` | ✔ | ✔ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
|
||||||
| `quality`¹ | ✔ | ✔ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
||||||
| `loop` | ✔ | ✔ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
||||||
| `source` | ✔ | ✔ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
|
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
||||||
| `poster`² | ✔ | ✔ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
|
||||||
| `autoplay` | ✔ | ✔ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
| `poster`² | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
||||||
| `language` | ✔ | ✔ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. |
|
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
||||||
| `pip` | ✔ | ✔ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
|
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. |
|
||||||
|
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
||||||
|
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
||||||
|
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
|
||||||
|
|
||||||
1. YouTube only. HTML5 will follow.
|
1. YouTube only. HTML5 will follow.
|
||||||
2. HTML5 only
|
2. HTML5 only
|
||||||
|
|
||||||
#### The `.source` setter
|
#### The `.source` setter
|
||||||
|
|
||||||
@ -461,7 +496,7 @@ _Note:_ `src` property for YouTube and Vimeo can either be the video ID or the w
|
|||||||
| `poster`¹ | String | The URL for the poster image (HTML5 video only). |
|
| `poster`¹ | String | The URL for the poster image (HTML5 video only). |
|
||||||
| `tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes. |
|
| `tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes. |
|
||||||
|
|
||||||
1. HTML5 only
|
1. HTML5 only
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
@ -531,8 +566,8 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
|
|||||||
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
||||||
third party APIs. More info on the respective API's here:
|
third party APIs. More info on the respective API's here:
|
||||||
|
|
||||||
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
||||||
* [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
* [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
||||||
|
|
||||||
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
||||||
|
|
||||||
@ -560,9 +595,9 @@ document then the shortcuts will work when any element has focus, apart from an
|
|||||||
Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias
|
Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias
|
||||||
Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
|
Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
|
||||||
|
|
||||||
* Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
|
* Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
|
||||||
* Using [Shaka](https://github.com/google/shaka-player) - [Demo](http://codepen.io/sampotts/pen/zBNpVR)
|
* Using [Shaka](https://github.com/google/shaka-player) - [Demo](http://codepen.io/sampotts/pen/zBNpVR)
|
||||||
* Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN)
|
* Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN)
|
||||||
|
|
||||||
## Fullscreen
|
## Fullscreen
|
||||||
|
|
||||||
@ -570,22 +605,25 @@ Fullscreen in Plyr is supported by all browsers that [currently support it](http
|
|||||||
|
|
||||||
## Browser support
|
## Browser support
|
||||||
|
|
||||||
Plyr supports the last 2 versions of most _modern_ browsers. IE11 is also supported.
|
Plyr supports the last 2 versions of most _modern_ browsers.
|
||||||
|
|
||||||
| Browser | Supported |
|
| Browser | Supported |
|
||||||
| ------------- | -------------- |
|
| ------------- | --------- |
|
||||||
| Safari | ✔ |
|
| Safari | ✓ |
|
||||||
| Mobile Safari | ✔¹ |
|
| Mobile Safari | ✓¹ |
|
||||||
| Firefox | ✔ |
|
| Firefox | ✓ |
|
||||||
| Chrome | ✔ |
|
| Chrome | ✓ |
|
||||||
| Opera | ✔ |
|
| Opera | ✓ |
|
||||||
| Edge | ✔ |
|
| Edge | ✓ |
|
||||||
| IE10+ | ✔² |
|
| IE11 | ✓ |
|
||||||
| IE9 | API only³ |
|
| IE10 | ✓² |
|
||||||
|
|
||||||
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled.
|
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled as they are handled device wide.
|
||||||
2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+)
|
2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported. No native fullscreen support, fallback can be used (see [options](#options))
|
||||||
3. IE10 has no native fullscreen support, fallback can be used (see [options](#options))
|
|
||||||
|
### Polyfills
|
||||||
|
|
||||||
|
Plyr uses ES6 which isn't supported in all browsers quite yet. This means some features will need to be polyfilled to be available otherwise you'll run into issues. We've elected to not burden the ~90% of users that do support these features with extra JS and instead leave polyfilling to you to work out based on your needs. The easiest method I've found is to use [polyfill.io](https://polyfill.io) which provides polyfills based on user agent. This is the method the demo uses.
|
||||||
|
|
||||||
### Checking for support
|
### Checking for support
|
||||||
|
|
||||||
@ -597,9 +635,9 @@ const supported = Plyr.supported('video', 'html5', true);
|
|||||||
|
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
* Media type (`audio` or `video`)
|
* Media type (`audio` or `video`)
|
||||||
* Provider (`html5`, `youtube` or `vimeo`)
|
* Provider (`html5`, `youtube` or `vimeo`)
|
||||||
* Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
* Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||||
|
|
||||||
### Disable support programatically
|
### Disable support programatically
|
||||||
|
|
||||||
@ -635,28 +673,28 @@ Plyr costs money to run, not only my time - I donate that for free but domains,
|
|||||||
|
|
||||||
## Mentions
|
## Mentions
|
||||||
|
|
||||||
* [ProductHunt](https://www.producthunt.com/tech/plyr)
|
* [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||||
* [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
* [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||||
* [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
* [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||||
* [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
* [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||||
* [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
* [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||||
* [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
* [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||||
* [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
* [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||||
* [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
* [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||||
* [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
* [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
||||||
* [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
* [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||||
|
|
||||||
## Used by
|
## Used by
|
||||||
|
|
||||||
* [Selz.com](https://selz.com)
|
* [Selz.com](https://selz.com)
|
||||||
* [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
* [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||||
* [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
* [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||||
* [TomTom.com](http://prioritydriving.tomtom.com/)
|
* [TomTom.com](http://prioritydriving.tomtom.com/)
|
||||||
* [DIGBMX](http://digbmx.com/)
|
* [DIGBMX](http://digbmx.com/)
|
||||||
* [Grime Archive](https://grimearchive.com/)
|
* [Grime Archive](https://grimearchive.com/)
|
||||||
* [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
* [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
||||||
* [Oscar Radio](http://oscar-radio.xyz/)
|
* [Oscar Radio](http://oscar-radio.xyz/)
|
||||||
* [Sparkk TV](https://www.sparkktv.com/)
|
* [Sparkk TV](https://www.sparkktv.com/)
|
||||||
|
|
||||||
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
||||||
|
|
||||||
@ -664,12 +702,12 @@ Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the abo
|
|||||||
|
|
||||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||||
|
|
||||||
* [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
* [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||||
* [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
* [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
[](https://www.fastly.com/)
|
[](https://www.fastly.com/)
|
||||||
|
|
||||||
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Captions
|
// Plyr Captions
|
||||||
|
// TODO: Create as class
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
@ -39,13 +40,12 @@ const captions = {
|
|||||||
// 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 (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the container
|
// Inject the container
|
||||||
if (!utils.is.element(this.elements.captions)) {
|
if (!utils.is.element(this.elements.captions)) {
|
||||||
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
||||||
@ -56,11 +56,42 @@ const captions = {
|
|||||||
// Set the class hook
|
// Set the class hook
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||||
|
|
||||||
|
// Get tracks
|
||||||
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
// If no caption file exists, hide container for caption text
|
// If no caption file exists, hide container for caption text
|
||||||
if (utils.is.empty(captions.getTracks.call(this))) {
|
if (utils.is.empty(tracks)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get browser info
|
||||||
|
const browser = utils.getBrowser();
|
||||||
|
|
||||||
|
// Fix IE captions if CORS is used
|
||||||
|
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
||||||
|
if (browser.isIE && window.URL) {
|
||||||
|
const elements = this.media.querySelectorAll('track');
|
||||||
|
|
||||||
|
Array.from(elements).forEach(track => {
|
||||||
|
const src = track.getAttribute('src');
|
||||||
|
const href = utils.parseUrl(src);
|
||||||
|
|
||||||
|
if (href.hostname !== window.location.href.hostname && [
|
||||||
|
'http:',
|
||||||
|
'https:',
|
||||||
|
].includes(href.protocol)) {
|
||||||
|
utils
|
||||||
|
.fetch(src, 'blob')
|
||||||
|
.then(blob => {
|
||||||
|
track.setAttribute('src', window.URL.createObjectURL(blob));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
utils.removeElement(track);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Set language
|
// Set language
|
||||||
captions.setLanguage.call(this);
|
captions.setLanguage.call(this);
|
||||||
|
|
||||||
@ -68,7 +99,7 @@ const captions = {
|
|||||||
captions.show.call(this);
|
captions.show.call(this);
|
||||||
|
|
||||||
// Set available languages in list
|
// Set available languages in list
|
||||||
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -78,7 +109,7 @@ const captions = {
|
|||||||
// Setup HTML5 track rendering
|
// Setup HTML5 track rendering
|
||||||
if (this.isHTML5 && this.isVideo) {
|
if (this.isHTML5 && this.isVideo) {
|
||||||
captions.getTracks.call(this).forEach(track => {
|
captions.getTracks.call(this).forEach(track => {
|
||||||
// Remove previous bindings
|
// Show track
|
||||||
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
|
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
|
||||||
|
|
||||||
// Turn off native caption rendering to avoid double captions
|
// Turn off native caption rendering to avoid double captions
|
||||||
@ -124,7 +155,8 @@ const captions = {
|
|||||||
setCue(input) {
|
setCue(input) {
|
||||||
// Get the track from the event if needed
|
// Get the track from the event if needed
|
||||||
const track = utils.is.event(input) ? input.target : input;
|
const track = utils.is.event(input) ? input.target : input;
|
||||||
const active = track.activeCues[0];
|
const { activeCues } = track;
|
||||||
|
const active = activeCues.length && activeCues[0];
|
||||||
const currentTrack = captions.getCurrentTrack.call(this);
|
const currentTrack = captions.getCurrentTrack.call(this);
|
||||||
|
|
||||||
// Only display current track
|
// Only display current track
|
||||||
|
147
src/js/controls.js
vendored
147
src/js/controls.js
vendored
@ -5,6 +5,7 @@
|
|||||||
import support from './support';
|
import support from './support';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
import i18n from './i18n';
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
|
|
||||||
// Sniff out the browser
|
// Sniff out the browser
|
||||||
@ -50,7 +51,7 @@ const controls = {
|
|||||||
icon,
|
icon,
|
||||||
utils.extend(attributes, {
|
utils.extend(attributes, {
|
||||||
role: 'presentation',
|
role: 'presentation',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the <use> to reference sprite
|
// Create the <use> to reference sprite
|
||||||
@ -74,7 +75,7 @@ const controls = {
|
|||||||
|
|
||||||
// Create hidden text label
|
// Create hidden text label
|
||||||
createLabel(type, attr) {
|
createLabel(type, attr) {
|
||||||
let text = this.config.i18n[type];
|
let text = i18n.get(type, this.config);
|
||||||
const attributes = Object.assign({}, attr);
|
const attributes = Object.assign({}, attr);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -115,8 +116,8 @@ const controls = {
|
|||||||
{
|
{
|
||||||
class: this.config.classNames.menu.badge,
|
class: this.config.classNames.menu.badge,
|
||||||
},
|
},
|
||||||
text
|
text,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return badge;
|
return badge;
|
||||||
@ -126,7 +127,7 @@ const controls = {
|
|||||||
createButton(buttonType, attr) {
|
createButton(buttonType, attr) {
|
||||||
const button = utils.createElement('button');
|
const button = utils.createElement('button');
|
||||||
const attributes = Object.assign({}, attr);
|
const attributes = Object.assign({}, attr);
|
||||||
let type = buttonType;
|
let type = utils.toCamelCase(buttonType);
|
||||||
|
|
||||||
let toggle = false;
|
let toggle = false;
|
||||||
let label;
|
let label;
|
||||||
@ -147,7 +148,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Large play button
|
// Large play button
|
||||||
switch (type) {
|
switch (buttonType) {
|
||||||
case 'play':
|
case 'play':
|
||||||
toggle = true;
|
toggle = true;
|
||||||
label = 'play';
|
label = 'play';
|
||||||
@ -189,7 +190,7 @@ const controls = {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
label = type;
|
label = type;
|
||||||
icon = type;
|
icon = buttonType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup toggle icon and labels
|
// Setup toggle icon and labels
|
||||||
@ -204,7 +205,7 @@ const controls = {
|
|||||||
|
|
||||||
// Add aria attributes
|
// Add aria attributes
|
||||||
attributes['aria-pressed'] = false;
|
attributes['aria-pressed'] = false;
|
||||||
attributes['aria-label'] = this.config.i18n[label];
|
attributes['aria-label'] = i18n.get(label, this.config);
|
||||||
} else {
|
} else {
|
||||||
button.appendChild(controls.createIcon.call(this, icon));
|
button.appendChild(controls.createIcon.call(this, icon));
|
||||||
button.appendChild(controls.createLabel.call(this, label));
|
button.appendChild(controls.createLabel.call(this, label));
|
||||||
@ -215,7 +216,16 @@ const controls = {
|
|||||||
|
|
||||||
utils.setAttributes(button, attributes);
|
utils.setAttributes(button, attributes);
|
||||||
|
|
||||||
this.elements.buttons[type] = button;
|
// We have multiple play buttons
|
||||||
|
if (type === 'play') {
|
||||||
|
if (!utils.is.array(this.elements.buttons[type])) {
|
||||||
|
this.elements.buttons[type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.elements.buttons[type].push(button);
|
||||||
|
} else {
|
||||||
|
this.elements.buttons[type] = button;
|
||||||
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
@ -229,7 +239,7 @@ const controls = {
|
|||||||
for: attributes.id,
|
for: attributes.id,
|
||||||
class: this.config.classNames.hidden,
|
class: this.config.classNames.hidden,
|
||||||
},
|
},
|
||||||
this.config.i18n[type]
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Seek input
|
// Seek input
|
||||||
@ -245,8 +255,8 @@ const controls = {
|
|||||||
value: 0,
|
value: 0,
|
||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
},
|
},
|
||||||
attributes
|
attributes,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.elements.inputs[type] = input;
|
this.elements.inputs[type] = input;
|
||||||
@ -271,8 +281,8 @@ const controls = {
|
|||||||
max: 100,
|
max: 100,
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
attributes
|
attributes,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the label inside
|
// Create the label inside
|
||||||
@ -282,11 +292,11 @@ const controls = {
|
|||||||
let suffix = '';
|
let suffix = '';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'played':
|
case 'played':
|
||||||
suffix = this.config.i18n.played;
|
suffix = i18n.get('played', this.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'buffer':
|
case 'buffer':
|
||||||
suffix = this.config.i18n.buffered;
|
suffix = i18n.get('buffered', this.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -313,8 +323,8 @@ const controls = {
|
|||||||
{
|
{
|
||||||
class: this.config.classNames.hidden,
|
class: this.config.classNames.hidden,
|
||||||
},
|
},
|
||||||
this.config.i18n[type]
|
i18n.get(type, this.config),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
||||||
@ -340,7 +350,7 @@ const controls = {
|
|||||||
value,
|
value,
|
||||||
checked,
|
checked,
|
||||||
class: 'plyr__sr-only',
|
class: 'plyr__sr-only',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const faux = utils.createElement('span', { 'aria-hidden': true });
|
const faux = utils.createElement('span', { 'aria-hidden': true });
|
||||||
@ -374,6 +384,16 @@ const controls = {
|
|||||||
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
|
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
|
||||||
const visible = `${this.config.classNames.tooltip}--visible`;
|
const visible = `${this.config.classNames.tooltip}--visible`;
|
||||||
|
|
||||||
|
const toggle = toggle => {
|
||||||
|
utils.toggleClass(this.elements.display.seekTooltip, visible, toggle);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hide on touch
|
||||||
|
if (this.touch) {
|
||||||
|
toggle(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine percentage, if already visible
|
// Determine percentage, if already visible
|
||||||
if (utils.is.event(event)) {
|
if (utils.is.event(event)) {
|
||||||
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
||||||
@ -402,7 +422,7 @@ const controls = {
|
|||||||
'mouseenter',
|
'mouseenter',
|
||||||
'mouseleave',
|
'mouseleave',
|
||||||
].includes(event.type)) {
|
].includes(event.type)) {
|
||||||
utils.toggleClass(this.elements.display.seekTooltip, visible, event.type === 'mouseenter');
|
toggle(event.type === 'mouseenter');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -418,6 +438,11 @@ const controls = {
|
|||||||
// Set the YouTube quality menu
|
// Set the YouTube quality menu
|
||||||
// TODO: Support for HTML5
|
// TODO: Support for HTML5
|
||||||
setQualityMenu(options) {
|
setQualityMenu(options) {
|
||||||
|
// Menu required
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.quality)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const type = 'quality';
|
const type = 'quality';
|
||||||
const list = this.elements.settings.panes.quality.querySelector('ul');
|
const list = this.elements.settings.panes.quality.querySelector('ul');
|
||||||
|
|
||||||
@ -473,7 +498,7 @@ const controls = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.options.quality.forEach(quality =>
|
this.options.quality.forEach(quality =>
|
||||||
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality))
|
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality)),
|
||||||
);
|
);
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
@ -526,7 +551,7 @@ const controls = {
|
|||||||
|
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'captions':
|
case 'captions':
|
||||||
value = this.captions.active ? this.captions.language : '';
|
value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -574,6 +599,11 @@ const controls = {
|
|||||||
|
|
||||||
// Set the looping options
|
// Set the looping options
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
|
// Menu required
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.loop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panes.loop.querySelector('ul');
|
const list = this.elements.settings.panes.loop.querySelector('ul');
|
||||||
|
|
||||||
@ -598,7 +628,7 @@ const controls = {
|
|||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
this.config.i18n[option]
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
@ -618,11 +648,7 @@ const controls = {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!support.textTracks || !captions.getTracks.call(this).length) {
|
if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
|
||||||
return this.config.i18n.none;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.captions.active) {
|
|
||||||
const currentTrack = captions.getCurrentTrack.call(this);
|
const currentTrack = captions.getCurrentTrack.call(this);
|
||||||
|
|
||||||
if (utils.is.track(currentTrack)) {
|
if (utils.is.track(currentTrack)) {
|
||||||
@ -630,7 +656,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.config.i18n.disabled;
|
return i18n.get('disabled', this.config);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@ -657,10 +683,10 @@ const controls = {
|
|||||||
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add the "None" option to turn off captions
|
// Add the "Disabled" option to turn off captions
|
||||||
tracks.unshift({
|
tracks.unshift({
|
||||||
language: '',
|
language: '',
|
||||||
label: this.config.i18n.none,
|
label: i18n.get('disabled', this.config),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate options
|
// Generate options
|
||||||
@ -672,7 +698,7 @@ const controls = {
|
|||||||
'language',
|
'language',
|
||||||
track.label || track.language,
|
track.label || track.language,
|
||||||
controls.createBadge.call(this, track.language.toUpperCase()),
|
controls.createBadge.call(this, track.language.toUpperCase()),
|
||||||
track.language.toLowerCase() === this.captions.language.toLowerCase()
|
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -680,11 +706,21 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
setSpeedMenu() {
|
setSpeedMenu(options) {
|
||||||
|
// Do nothing if not selected
|
||||||
|
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu required
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.speed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const type = 'speed';
|
const type = 'speed';
|
||||||
|
|
||||||
// Set the default speeds
|
// Set the speed options
|
||||||
if (!utils.is.object(this.options.speed) || !Object.keys(this.options.speed).length) {
|
if (!utils.is.array(options)) {
|
||||||
this.options.speed = [
|
this.options.speed = [
|
||||||
0.5,
|
0.5,
|
||||||
0.75,
|
0.75,
|
||||||
@ -694,6 +730,8 @@ const controls = {
|
|||||||
1.75,
|
1.75,
|
||||||
2,
|
2,
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
this.options.speed = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set options if passed and filter based on config
|
// Set options if passed and filter based on config
|
||||||
@ -703,6 +741,9 @@ const controls = {
|
|||||||
const toggle = !utils.is.empty(this.options.speed);
|
const toggle = !utils.is.empty(this.options.speed);
|
||||||
controls.toggleTab.call(this, type, toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
|
// Check if we need to toggle the parent
|
||||||
|
controls.checkMenu.call(this);
|
||||||
|
|
||||||
// If we're hiding, nothing more to do
|
// If we're hiding, nothing more to do
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
return;
|
return;
|
||||||
@ -724,10 +765,24 @@ const controls = {
|
|||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Check if we need to hide/show the settings menu
|
||||||
|
checkMenu() {
|
||||||
|
const speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null;
|
||||||
|
const languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
|
||||||
|
|
||||||
|
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden);
|
||||||
|
},
|
||||||
|
|
||||||
// Show/hide menu
|
// Show/hide menu
|
||||||
toggleMenu(event) {
|
toggleMenu(event) {
|
||||||
const { form } = this.elements.settings;
|
const { form } = this.elements.settings;
|
||||||
const button = this.elements.buttons.settings;
|
const button = this.elements.buttons.settings;
|
||||||
|
|
||||||
|
// Menu and button are required
|
||||||
|
if (!utils.is.element(form) || !utils.is.element(button)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true';
|
const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true';
|
||||||
|
|
||||||
if (utils.is.event(event)) {
|
if (utils.is.event(event)) {
|
||||||
@ -893,7 +948,6 @@ const controls = {
|
|||||||
// Play/Pause button
|
// Play/Pause button
|
||||||
if (this.config.controls.includes('play')) {
|
if (this.config.controls.includes('play')) {
|
||||||
container.appendChild(controls.createButton.call(this, 'play'));
|
container.appendChild(controls.createButton.call(this, 'play'));
|
||||||
// container.appendChild(controls.createButton.call(this, 'pause'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast forward button
|
// Fast forward button
|
||||||
@ -925,7 +979,7 @@ const controls = {
|
|||||||
role: 'tooltip',
|
role: 'tooltip',
|
||||||
class: this.config.classNames.tooltip,
|
class: this.config.classNames.tooltip,
|
||||||
},
|
},
|
||||||
'00:00'
|
'00:00',
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.appendChild(tooltip);
|
progress.appendChild(tooltip);
|
||||||
@ -970,7 +1024,7 @@ const controls = {
|
|||||||
'volume',
|
'volume',
|
||||||
utils.extend(attributes, {
|
utils.extend(attributes, {
|
||||||
id: `plyr-volume-${data.id}`,
|
id: `plyr-volume-${data.id}`,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
volume.appendChild(range.label);
|
volume.appendChild(range.label);
|
||||||
volume.appendChild(range.input);
|
volume.appendChild(range.input);
|
||||||
@ -997,7 +1051,7 @@ const controls = {
|
|||||||
'aria-haspopup': true,
|
'aria-haspopup': true,
|
||||||
'aria-controls': `plyr-settings-${data.id}`,
|
'aria-controls': `plyr-settings-${data.id}`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = utils.createElement('form', {
|
const form = utils.createElement('form', {
|
||||||
@ -1040,7 +1094,7 @@ const controls = {
|
|||||||
'aria-controls': `plyr-settings-${data.id}-${type}`,
|
'aria-controls': `plyr-settings-${data.id}-${type}`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
}),
|
}),
|
||||||
this.config.i18n[type]
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = utils.createElement('span', {
|
const value = utils.createElement('span', {
|
||||||
@ -1080,7 +1134,7 @@ const controls = {
|
|||||||
'aria-controls': `plyr-settings-${data.id}-home`,
|
'aria-controls': `plyr-settings-${data.id}-home`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
},
|
},
|
||||||
this.config.i18n[type]
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
pane.appendChild(back);
|
pane.appendChild(back);
|
||||||
@ -1123,7 +1177,7 @@ const controls = {
|
|||||||
|
|
||||||
this.elements.controls = container;
|
this.elements.controls = container;
|
||||||
|
|
||||||
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
|
if (this.isHTML5) {
|
||||||
controls.setSpeedMenu.call(this);
|
controls.setSpeedMenu.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1147,9 +1201,10 @@ const controls = {
|
|||||||
|
|
||||||
// Null by default
|
// Null by default
|
||||||
let container = null;
|
let container = null;
|
||||||
|
this.elements.controls = null;
|
||||||
|
|
||||||
// HTML passed as the option
|
// HTML or Element passed as the option
|
||||||
if (utils.is.string(this.config.controls)) {
|
if (utils.is.string(this.config.controls) || utils.is.element(this.config.controls)) {
|
||||||
container = this.config.controls;
|
container = this.config.controls;
|
||||||
} else if (utils.is.function(this.config.controls)) {
|
} else if (utils.is.function(this.config.controls)) {
|
||||||
// A custom function to build controls
|
// A custom function to build controls
|
||||||
@ -1193,7 +1248,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the elements if need be
|
// Find the elements if need be
|
||||||
if (utils.is.element(this.elements.controls)) {
|
if (!utils.is.element(this.elements.controls)) {
|
||||||
utils.findElements.call(this);
|
utils.findElements.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1212,7 +1267,7 @@ const controls = {
|
|||||||
this.config.selectors.labels,
|
this.config.selectors.labels,
|
||||||
' .',
|
' .',
|
||||||
this.config.classNames.hidden,
|
this.config.classNames.hidden,
|
||||||
].join('')
|
].join(''),
|
||||||
);
|
);
|
||||||
|
|
||||||
Array.from(labels).forEach(label => {
|
Array.from(labels).forEach(label => {
|
||||||
|
@ -56,7 +56,7 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/2.0.10/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.0.7/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -120,6 +120,7 @@ const defaults = {
|
|||||||
fullscreen: {
|
fullscreen: {
|
||||||
enabled: true, // Allow fullscreen?
|
enabled: true, // Allow fullscreen?
|
||||||
fallback: true, // Fallback for vintage browsers
|
fallback: true, // Fallback for vintage browsers
|
||||||
|
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Local storage
|
// Local storage
|
||||||
@ -131,7 +132,10 @@ const defaults = {
|
|||||||
// Default controls
|
// Default controls
|
||||||
controls: [
|
controls: [
|
||||||
'play-large',
|
'play-large',
|
||||||
|
// 'restart',
|
||||||
|
// 'rewind',
|
||||||
'play',
|
'play',
|
||||||
|
// 'fast-forward',
|
||||||
'progress',
|
'progress',
|
||||||
'current-time',
|
'current-time',
|
||||||
'mute',
|
'mute',
|
||||||
@ -154,7 +158,7 @@ const defaults = {
|
|||||||
rewind: 'Rewind {seektime} secs',
|
rewind: 'Rewind {seektime} secs',
|
||||||
play: 'Play',
|
play: 'Play',
|
||||||
pause: 'Pause',
|
pause: 'Pause',
|
||||||
forward: 'Forward {seektime} secs',
|
fastForward: 'Forward {seektime} secs',
|
||||||
seek: 'Seek',
|
seek: 'Seek',
|
||||||
played: 'Played',
|
played: 'Played',
|
||||||
buffered: 'Buffered',
|
buffered: 'Buffered',
|
||||||
@ -177,9 +181,8 @@ const defaults = {
|
|||||||
end: 'End',
|
end: 'End',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
none: 'None',
|
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
advertisment: 'Ad',
|
advertisement: 'Ad',
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -202,7 +205,7 @@ const defaults = {
|
|||||||
pause: null,
|
pause: null,
|
||||||
restart: null,
|
restart: null,
|
||||||
rewind: null,
|
rewind: null,
|
||||||
forward: null,
|
fastForward: null,
|
||||||
mute: null,
|
mute: null,
|
||||||
volume: null,
|
volume: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
@ -258,7 +261,7 @@ const defaults = {
|
|||||||
// Ads
|
// Ads
|
||||||
'adsloaded',
|
'adsloaded',
|
||||||
'adscontentpause',
|
'adscontentpause',
|
||||||
'adsconentresume',
|
'adscontentresume',
|
||||||
'adstarted',
|
'adstarted',
|
||||||
'adsmidpoint',
|
'adsmidpoint',
|
||||||
'adscomplete',
|
'adscomplete',
|
||||||
@ -282,7 +285,7 @@ const defaults = {
|
|||||||
pause: '[data-plyr="pause"]',
|
pause: '[data-plyr="pause"]',
|
||||||
restart: '[data-plyr="restart"]',
|
restart: '[data-plyr="restart"]',
|
||||||
rewind: '[data-plyr="rewind"]',
|
rewind: '[data-plyr="rewind"]',
|
||||||
forward: '[data-plyr="fast-forward"]',
|
fastForward: '[data-plyr="fast-forward"]',
|
||||||
mute: '[data-plyr="mute"]',
|
mute: '[data-plyr="mute"]',
|
||||||
captions: '[data-plyr="captions"]',
|
captions: '[data-plyr="captions"]',
|
||||||
fullscreen: '[data-plyr="fullscreen"]',
|
fullscreen: '[data-plyr="fullscreen"]',
|
||||||
@ -358,15 +361,24 @@ const defaults = {
|
|||||||
tabFocus: 'plyr__tab-focus',
|
tabFocus: 'plyr__tab-focus',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Embed attributes
|
||||||
|
attributes: {
|
||||||
|
embed: {
|
||||||
|
provider: 'data-plyr-provider',
|
||||||
|
id: 'data-plyr-embed-id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// API keys
|
// API keys
|
||||||
keys: {
|
keys: {
|
||||||
google: null,
|
google: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Advertisements plugin
|
// Advertisements plugin
|
||||||
// Tag is not required as publisher is determined by vi.ai using the domain
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
ads: {
|
ads: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
publisherId: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,127 +1,211 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr fullscreen API
|
// Fullscreen wrapper
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
|
|
||||||
// Determine the prefix
|
const browser = utils.getBrowser();
|
||||||
const prefix = (() => {
|
|
||||||
let value = false;
|
|
||||||
|
|
||||||
if (utils.is.function(document.cancelFullScreen)) {
|
function onChange() {
|
||||||
value = '';
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update toggle button
|
||||||
|
const button = this.player.elements.buttons.fullscreen;
|
||||||
|
if (utils.is.element(button)) {
|
||||||
|
utils.toggleState(button, this.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger an event
|
||||||
|
utils.dispatchEvent(this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||||
|
|
||||||
|
// Trap focus in container
|
||||||
|
if (!browser.isIos) {
|
||||||
|
utils.trapFocus.call(this.player, this.target, this.active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFallback(toggle = false) {
|
||||||
|
// Store or restore scroll position
|
||||||
|
if (toggle) {
|
||||||
|
this.scrollPosition = {
|
||||||
|
x: window.scrollX || 0,
|
||||||
|
y: window.scrollY || 0,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
|
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle scroll
|
||||||
|
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||||
|
|
||||||
|
// Toggle class hook
|
||||||
|
utils.toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||||
|
|
||||||
|
// Toggle button and fire events
|
||||||
|
onChange.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fullscreen {
|
||||||
|
constructor(player) {
|
||||||
|
// Keep reference to parent
|
||||||
|
this.player = player;
|
||||||
|
|
||||||
|
// Get prefix
|
||||||
|
this.prefix = Fullscreen.prefix;
|
||||||
|
this.name = Fullscreen.name;
|
||||||
|
|
||||||
|
// Scroll position
|
||||||
|
this.scrollPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// Register event listeners
|
||||||
|
// Handle event (incase user presses escape etc)
|
||||||
|
utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {
|
||||||
|
// TODO: Filter for target??
|
||||||
|
onChange.call(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fullscreen toggle on double click
|
||||||
|
utils.on(this.player.elements.container, 'dblclick', () => {
|
||||||
|
this.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent double click on controls bubbling up
|
||||||
|
utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if native supported
|
||||||
|
static get native() {
|
||||||
|
return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the prefix for handlers
|
||||||
|
static get prefix() {
|
||||||
|
// No prefix
|
||||||
|
if (utils.is.function(document.exitFullscreen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for fullscreen support by vendor prefix
|
// Check for fullscreen support by vendor prefix
|
||||||
[
|
let value = '';
|
||||||
|
const prefixes = [
|
||||||
'webkit',
|
'webkit',
|
||||||
'o',
|
|
||||||
'moz',
|
'moz',
|
||||||
'ms',
|
'ms',
|
||||||
'khtml',
|
];
|
||||||
].some(pre => {
|
|
||||||
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
|
prefixes.some(pre => {
|
||||||
|
if (utils.is.function(document[`${pre}ExitFullscreen`]) || utils.is.function(document[`${pre}CancelFullScreen`])) {
|
||||||
value = pre;
|
value = pre;
|
||||||
return true;
|
return true;
|
||||||
} else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) {
|
|
||||||
// Special case for MS (when isn't it?)
|
|
||||||
value = 'ms';
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
static get name() {
|
||||||
})();
|
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||||
|
}
|
||||||
|
|
||||||
// Fullscreen API
|
// Determine if fullscreen is enabled
|
||||||
const fullscreen = {
|
get enabled() {
|
||||||
// Get the prefix
|
return (
|
||||||
prefix,
|
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
||||||
|
this.player.config.fullscreen.enabled &&
|
||||||
|
this.player.supported.ui &&
|
||||||
|
this.player.isVideo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we can use it
|
// Get active state
|
||||||
enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled,
|
get active() {
|
||||||
|
if (!this.enabled) {
|
||||||
// Yet again Microsoft awesomeness,
|
|
||||||
// Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
|
|
||||||
eventType: prefix === 'ms' ? 'MSFullscreenChange' : `${prefix}fullscreenchange`,
|
|
||||||
|
|
||||||
// Is an element fullscreen
|
|
||||||
isFullScreen(element) {
|
|
||||||
if (!fullscreen.enabled) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
// Fallback using classname
|
||||||
|
if (!Fullscreen.native) {
|
||||||
switch (prefix) {
|
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||||
case '':
|
|
||||||
return document.fullscreenElement === target;
|
|
||||||
|
|
||||||
case 'moz':
|
|
||||||
return document.mozFullScreenElement === target;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return document[`${prefix}FullscreenElement`] === target;
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.name}Element`];
|
||||||
|
|
||||||
|
return element === this.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target element
|
||||||
|
get target() {
|
||||||
|
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
update() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.player.debug.log(`${Fullscreen.native ? 'Native' : 'Fallback'} fullscreen enabled`);
|
||||||
|
} else {
|
||||||
|
this.player.debug.log('Fullscreen not supported and fallback disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add styling hook to show button
|
||||||
|
utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
// Make an element fullscreen
|
// Make an element fullscreen
|
||||||
requestFullScreen(element) {
|
enter() {
|
||||||
if (!fullscreen.enabled) {
|
if (!this.enabled) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
|
||||||
|
|
||||||
return !prefix.length ? target.requestFullScreen() : target[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Bail from fullscreen
|
|
||||||
cancelFullScreen() {
|
|
||||||
if (!fullscreen.enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !prefix.length ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the current element
|
|
||||||
element() {
|
|
||||||
if (!fullscreen.enabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !prefix.length ? document.fullscreenElement : document[`${prefix}FullscreenElement`];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Setup fullscreen
|
|
||||||
setup() {
|
|
||||||
if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for native support
|
// iOS native fullscreen doesn't need the request step
|
||||||
const nativeSupport = fullscreen.enabled;
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
if (this.player.playing) {
|
||||||
|
this.target.webkitEnterFullscreen();
|
||||||
|
}
|
||||||
|
} else if (!Fullscreen.native) {
|
||||||
|
toggleFallback.call(this, true);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
this.target.requestFullscreen();
|
||||||
|
} else if (!utils.is.empty(this.prefix)) {
|
||||||
|
this.target[`${this.prefix}Request${this.name}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) {
|
// Bail from fullscreen
|
||||||
this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`);
|
exit() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add styling hook to show button
|
// iOS native fullscreen
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true);
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
this.target.webkitExitFullscreen();
|
||||||
|
this.player.play();
|
||||||
|
} else if (!Fullscreen.native) {
|
||||||
|
toggleFallback.call(this, false);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
document.cancelFullScreen();
|
||||||
|
} else if (!utils.is.empty(this.prefix)) {
|
||||||
|
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
||||||
|
document[`${this.prefix}${action}${this.name}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle state
|
||||||
|
toggle() {
|
||||||
|
if (!this.active) {
|
||||||
|
this.enter();
|
||||||
} else {
|
} else {
|
||||||
this.debug.log('Fullscreen not supported and fallback disabled');
|
this.exit();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle state
|
export default Fullscreen;
|
||||||
if (this.elements.buttons && this.elements.buttons.fullscreen) {
|
|
||||||
utils.toggleState(this.elements.buttons.fullscreen, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trap focus in container
|
|
||||||
utils.trapFocus.call(this);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default fullscreen;
|
|
||||||
|
31
src/js/i18n.js
Normal file
31
src/js/i18n.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Plyr internationalization
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
import utils from './utils';
|
||||||
|
|
||||||
|
const i18n = {
|
||||||
|
get(key = '', config = {}) {
|
||||||
|
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = config.i18n[key];
|
||||||
|
|
||||||
|
const replace = {
|
||||||
|
'{seektime}': config.seekTime,
|
||||||
|
'{title}': config.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(replace).forEach(([
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
]) => {
|
||||||
|
string = utils.replaceAll(string, key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return string;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default i18n;
|
@ -5,276 +5,295 @@
|
|||||||
import support from './support';
|
import support from './support';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import fullscreen from './fullscreen';
|
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
|
||||||
// Sniff out the browser
|
// Sniff out the browser
|
||||||
const browser = utils.getBrowser();
|
const browser = utils.getBrowser();
|
||||||
|
|
||||||
const listeners = {
|
class Listeners {
|
||||||
// Global listeners
|
constructor(player) {
|
||||||
global() {
|
this.player = player;
|
||||||
let last = null;
|
this.lastKey = null;
|
||||||
|
|
||||||
// Get the key code for an event
|
this.handleKey = this.handleKey.bind(this);
|
||||||
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
|
this.toggleMenu = this.toggleMenu.bind(this);
|
||||||
|
this.firstTouch = this.firstTouch.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle key press
|
// Handle key presses
|
||||||
const handleKey = event => {
|
handleKey(event) {
|
||||||
const code = getKeyCode(event);
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const pressed = event.type === 'keydown';
|
const pressed = event.type === 'keydown';
|
||||||
const repeat = pressed && code === last;
|
const repeat = pressed && code === this.lastKey;
|
||||||
|
|
||||||
// Bail if a modifier key is set
|
// Bail if a modifier key is set
|
||||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the event is bubbled from the media element
|
// If the event is bubbled from the media element
|
||||||
// Firefox doesn't get the keycode for whatever reason
|
// Firefox doesn't get the keycode for whatever reason
|
||||||
if (!utils.is.number(code)) {
|
if (!utils.is.number(code)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek by the number keys
|
// Seek by the number keys
|
||||||
const seekByKey = () => {
|
const seekByKey = () => {
|
||||||
// Divide the max duration into 10th's and times by the number value
|
// Divide the max duration into 10th's and times by the number value
|
||||||
this.currentTime = this.duration / 10 * (code - 48);
|
this.player.currentTime = this.player.duration / 10 * (code - 48);
|
||||||
};
|
|
||||||
|
|
||||||
// Handle the key on keydown
|
|
||||||
// Reset on keyup
|
|
||||||
if (pressed) {
|
|
||||||
// Which keycodes should we prevent default
|
|
||||||
const preventDefault = [
|
|
||||||
48,
|
|
||||||
49,
|
|
||||||
50,
|
|
||||||
51,
|
|
||||||
52,
|
|
||||||
53,
|
|
||||||
54,
|
|
||||||
56,
|
|
||||||
57,
|
|
||||||
32,
|
|
||||||
75,
|
|
||||||
38,
|
|
||||||
40,
|
|
||||||
77,
|
|
||||||
39,
|
|
||||||
37,
|
|
||||||
70,
|
|
||||||
67,
|
|
||||||
73,
|
|
||||||
76,
|
|
||||||
79,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check focused element
|
|
||||||
// and if the focused element is not editable (e.g. text input)
|
|
||||||
// and any that accept key input http://webaim.org/techniques/keyboard/
|
|
||||||
const focused = utils.getFocusElement();
|
|
||||||
if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
|
||||||
if (preventDefault.includes(code)) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (code) {
|
|
||||||
case 48:
|
|
||||||
case 49:
|
|
||||||
case 50:
|
|
||||||
case 51:
|
|
||||||
case 52:
|
|
||||||
case 53:
|
|
||||||
case 54:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
// 0-9
|
|
||||||
if (!repeat) {
|
|
||||||
seekByKey();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 32:
|
|
||||||
case 75:
|
|
||||||
// Space and K key
|
|
||||||
if (!repeat) {
|
|
||||||
this.togglePlay();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 38:
|
|
||||||
// Arrow up
|
|
||||||
this.increaseVolume(0.1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 40:
|
|
||||||
// Arrow down
|
|
||||||
this.decreaseVolume(0.1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 77:
|
|
||||||
// M key
|
|
||||||
if (!repeat) {
|
|
||||||
this.muted = !this.muted;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 39:
|
|
||||||
// Arrow forward
|
|
||||||
this.forward();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 37:
|
|
||||||
// Arrow back
|
|
||||||
this.rewind();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 70:
|
|
||||||
// F key
|
|
||||||
this.toggleFullscreen();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 67:
|
|
||||||
// C key
|
|
||||||
if (!repeat) {
|
|
||||||
this.toggleCaptions();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 76:
|
|
||||||
// L key
|
|
||||||
this.loop = !this.loop;
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* case 73:
|
|
||||||
this.setLoop('start');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 76:
|
|
||||||
this.setLoop();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 79:
|
|
||||||
this.setLoop('end');
|
|
||||||
break; */
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape is handle natively when in full screen
|
|
||||||
// So we only need to worry about non native
|
|
||||||
if (!fullscreen.enabled && this.fullscreen.active && code === 27) {
|
|
||||||
this.toggleFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store last code for next cycle
|
|
||||||
last = code;
|
|
||||||
} else {
|
|
||||||
last = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle the key on keydown
|
||||||
|
// Reset on keyup
|
||||||
|
if (pressed) {
|
||||||
|
// Which keycodes should we prevent default
|
||||||
|
const preventDefault = [
|
||||||
|
48,
|
||||||
|
49,
|
||||||
|
50,
|
||||||
|
51,
|
||||||
|
52,
|
||||||
|
53,
|
||||||
|
54,
|
||||||
|
56,
|
||||||
|
57,
|
||||||
|
32,
|
||||||
|
75,
|
||||||
|
38,
|
||||||
|
40,
|
||||||
|
77,
|
||||||
|
39,
|
||||||
|
37,
|
||||||
|
70,
|
||||||
|
67,
|
||||||
|
73,
|
||||||
|
76,
|
||||||
|
79,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check focused element
|
||||||
|
// and if the focused element is not editable (e.g. text input)
|
||||||
|
// and any that accept key input http://webaim.org/techniques/keyboard/
|
||||||
|
const focused = utils.getFocusElement();
|
||||||
|
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
||||||
|
if (preventDefault.includes(code)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 48:
|
||||||
|
case 49:
|
||||||
|
case 50:
|
||||||
|
case 51:
|
||||||
|
case 52:
|
||||||
|
case 53:
|
||||||
|
case 54:
|
||||||
|
case 55:
|
||||||
|
case 56:
|
||||||
|
case 57:
|
||||||
|
// 0-9
|
||||||
|
if (!repeat) {
|
||||||
|
seekByKey();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
case 75:
|
||||||
|
// Space and K key
|
||||||
|
if (!repeat) {
|
||||||
|
this.player.togglePlay();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 38:
|
||||||
|
// Arrow up
|
||||||
|
this.player.increaseVolume(0.1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 40:
|
||||||
|
// Arrow down
|
||||||
|
this.player.decreaseVolume(0.1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 77:
|
||||||
|
// M key
|
||||||
|
if (!repeat) {
|
||||||
|
this.player.muted = !this.player.muted;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 39:
|
||||||
|
// Arrow forward
|
||||||
|
this.player.forward();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 37:
|
||||||
|
// Arrow back
|
||||||
|
this.player.rewind();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 70:
|
||||||
|
// F key
|
||||||
|
this.player.fullscreen.toggle();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 67:
|
||||||
|
// C key
|
||||||
|
if (!repeat) {
|
||||||
|
this.player.toggleCaptions();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 76:
|
||||||
|
// L key
|
||||||
|
this.player.loop = !this.player.loop;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* case 73:
|
||||||
|
this.setLoop('start');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 76:
|
||||||
|
this.setLoop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 79:
|
||||||
|
this.setLoop('end');
|
||||||
|
break; */
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape is handle natively when in full screen
|
||||||
|
// So we only need to worry about non native
|
||||||
|
if (!this.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
|
||||||
|
this.player.fullscreen.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store last code for next cycle
|
||||||
|
this.lastKey = code;
|
||||||
|
} else {
|
||||||
|
this.lastKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle menu
|
||||||
|
toggleMenu(event) {
|
||||||
|
controls.toggleMenu.call(this.player, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device is touch enabled
|
||||||
|
firstTouch() {
|
||||||
|
this.player.touch = true;
|
||||||
|
|
||||||
|
// Add touch class
|
||||||
|
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
utils.off(document.body, 'touchstart', this.firstTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global window & document listeners
|
||||||
|
global(toggle = true) {
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
if (this.config.keyboard.global) {
|
if (this.player.config.keyboard.global) {
|
||||||
utils.on(window, 'keydown keyup', handleKey, false);
|
utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
|
||||||
} else if (this.config.keyboard.focused) {
|
}
|
||||||
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
|
|
||||||
|
// Click anywhere closes menu
|
||||||
|
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
|
||||||
|
|
||||||
|
// Detect touch by events
|
||||||
|
utils.on(document.body, 'touchstart', this.firstTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container listeners
|
||||||
|
container() {
|
||||||
|
// Keyboard shortcuts
|
||||||
|
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
|
||||||
|
utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect tab focus
|
// Detect tab focus
|
||||||
// Remove class on blur/focusout
|
// Remove class on blur/focusout
|
||||||
utils.on(this.elements.container, 'focusout', event => {
|
utils.on(this.player.elements.container, 'focusout', event => {
|
||||||
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
|
utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Add classname to tabbed elements
|
||||||
utils.on(this.elements.container, 'keydown', event => {
|
utils.on(this.player.elements.container, 'keydown', event => {
|
||||||
if (event.keyCode !== 9) {
|
if (event.keyCode !== 9) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
// Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
|
utils.toggleClass(utils.getFocusElement(), this.player.config.classNames.tabFocus, true);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle controls visibility based on mouse movement
|
// Toggle controls visibility based on mouse movement
|
||||||
if (this.config.hideControls) {
|
if (this.player.config.hideControls) {
|
||||||
// Toggle controls on mouse events and entering fullscreen
|
// Toggle controls on mouse events and entering fullscreen
|
||||||
utils.on(this.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
|
utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
|
||||||
this.toggleControls(event);
|
this.player.toggleControls(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Handle user exiting fullscreen by escaping etc
|
|
||||||
if (fullscreen.enabled) {
|
|
||||||
utils.on(document, fullscreen.eventType, event => {
|
|
||||||
this.toggleFullscreen(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fullscreen toggle on double click
|
|
||||||
utils.on(this.elements.container, 'dblclick', event => {
|
|
||||||
this.toggleFullscreen(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Listen for media events
|
// Listen for media events
|
||||||
media() {
|
media() {
|
||||||
// Time change on media
|
// Time change on media
|
||||||
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
|
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Display duration
|
// Display duration
|
||||||
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
|
utils.on(this.player.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Check for audio tracks on load
|
// Check for audio tracks on load
|
||||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||||
utils.on(this.media, 'loadeddata', () => {
|
utils.on(this.player.media, 'loadeddata', () => {
|
||||||
utils.toggleHidden(this.elements.volume, !this.hasAudio);
|
utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio);
|
||||||
utils.toggleHidden(this.elements.buttons.mute, !this.hasAudio);
|
utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle the media finishing
|
// Handle the media finishing
|
||||||
utils.on(this.media, 'ended', () => {
|
utils.on(this.player.media, 'ended', () => {
|
||||||
// Show poster on end
|
// Show poster on end
|
||||||
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
|
if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) {
|
||||||
// Restart
|
// Restart
|
||||||
this.restart();
|
this.player.restart();
|
||||||
|
|
||||||
// Re-load media
|
// Re-load media
|
||||||
this.media.load();
|
this.player.media.load();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for buffer progress
|
// Check for buffer progress
|
||||||
utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
|
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
|
||||||
|
|
||||||
// Handle native mute
|
// Handle native mute
|
||||||
utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
|
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
|
||||||
|
|
||||||
// Handle native play/pause
|
// Handle native play/pause
|
||||||
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
|
utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event));
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
|
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
|
||||||
|
|
||||||
// Check if media failed to load
|
// Check if media failed to load
|
||||||
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
|
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
|
||||||
|
|
||||||
// Click video
|
// Click video
|
||||||
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
|
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
||||||
// Re-fetch the wrapper
|
// Re-fetch the wrapper
|
||||||
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
|
const wrapper = utils.getElement.call(this.player, `.${this.player.config.classNames.video}`);
|
||||||
|
|
||||||
// Bail if there's no wrapper (this should never happen)
|
// Bail if there's no wrapper (this should never happen)
|
||||||
if (!utils.is.element(wrapper)) {
|
if (!utils.is.element(wrapper)) {
|
||||||
@ -284,78 +303,78 @@ const listeners = {
|
|||||||
// On click play, pause ore restart
|
// On click play, pause ore restart
|
||||||
utils.on(wrapper, 'click', () => {
|
utils.on(wrapper, 'click', () => {
|
||||||
// Touch devices will just show controls (if we're hiding controls)
|
// Touch devices will just show controls (if we're hiding controls)
|
||||||
if (this.config.hideControls && support.touch && !this.paused) {
|
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.paused) {
|
if (this.player.paused) {
|
||||||
this.play();
|
this.player.play();
|
||||||
} else if (this.ended) {
|
} else if (this.player.ended) {
|
||||||
this.restart();
|
this.player.restart();
|
||||||
this.play();
|
this.player.play();
|
||||||
} else {
|
} else {
|
||||||
this.pause();
|
this.player.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable right click
|
// Disable right click
|
||||||
if (this.supported.ui && this.config.disableContextMenu) {
|
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
||||||
utils.on(
|
utils.on(
|
||||||
this.media,
|
this.player.media,
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
event => {
|
event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume change
|
// Volume change
|
||||||
utils.on(this.media, 'volumechange', () => {
|
utils.on(this.player.media, 'volumechange', () => {
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ volume: this.volume, muted: this.muted });
|
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Speed change
|
// Speed change
|
||||||
utils.on(this.media, 'ratechange', () => {
|
utils.on(this.player.media, 'ratechange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'speed');
|
controls.updateSetting.call(this.player, 'speed');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ speed: this.speed });
|
this.player.storage.set({ speed: this.player.speed });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality change
|
// Quality change
|
||||||
utils.on(this.media, 'qualitychange', () => {
|
utils.on(this.player.media, 'qualitychange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'quality');
|
controls.updateSetting.call(this.player, 'quality');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ quality: this.quality });
|
this.player.storage.set({ quality: this.player.quality });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Caption language change
|
// Caption language change
|
||||||
utils.on(this.media, 'languagechange', () => {
|
utils.on(this.player.media, 'languagechange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this.player, 'captions');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ language: this.language });
|
this.player.storage.set({ language: this.player.language });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
utils.on(this.media, 'captionsenabled captionsdisabled', () => {
|
utils.on(this.player.media, 'captionsenabled captionsdisabled', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this.player, 'captions');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ captions: this.captions.active });
|
this.player.storage.set({ captions: this.player.captions.active });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Proxy events to container
|
// Proxy events to container
|
||||||
// Bubble up key events for Edge
|
// Bubble up key events for Edge
|
||||||
utils.on(this.media, this.config.events.concat([
|
utils.on(this.player.media, this.player.config.events.concat([
|
||||||
'keyup',
|
'keyup',
|
||||||
'keydown',
|
'keydown',
|
||||||
]).join(' '), event => {
|
]).join(' '), event => {
|
||||||
@ -363,229 +382,242 @@ const listeners = {
|
|||||||
|
|
||||||
// Get error details from media
|
// Get error details from media
|
||||||
if (event.type === 'error') {
|
if (event.type === 'error') {
|
||||||
detail = this.media.error;
|
detail = this.player.media.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail);
|
utils.dispatchEvent.call(this.player, this.player.elements.container, event.type, true, detail);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// Listen for control events
|
// Listen for control events
|
||||||
controls() {
|
controls() {
|
||||||
// IE doesn't support input event, so we fallback to change
|
// IE doesn't support input event, so we fallback to change
|
||||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||||
|
|
||||||
// Trigger custom and default handlers
|
// Run default and custom handlers
|
||||||
const proxy = (event, handlerKey, defaultHandler) => {
|
const proxy = (event, defaultHandler, customHandlerKey) => {
|
||||||
const customHandler = this.config.listeners[handlerKey];
|
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = utils.is.function(customHandler);
|
||||||
|
let returned = true;
|
||||||
|
|
||||||
// Execute custom handler
|
// Execute custom handler
|
||||||
if (utils.is.function(customHandler)) {
|
if (hasCustomHandler) {
|
||||||
customHandler.call(this, event);
|
returned = customHandler.call(this.player, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only call default handler if not prevented in custom handler
|
// Only call default handler if not prevented in custom handler
|
||||||
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
|
if (returned && utils.is.function(defaultHandler)) {
|
||||||
defaultHandler.call(this, event);
|
defaultHandler.call(this.player, event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trigger custom and default handlers
|
||||||
|
const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
|
||||||
|
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = utils.is.function(customHandler);
|
||||||
|
|
||||||
|
utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
|
||||||
|
};
|
||||||
|
|
||||||
// Play/pause toggle
|
// Play/pause toggle
|
||||||
utils.on(this.elements.buttons.play, 'click', event =>
|
on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
|
||||||
proxy(event, 'play', () => {
|
|
||||||
this.togglePlay();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
utils.on(this.elements.buttons.restart, 'click', event =>
|
on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
|
||||||
proxy(event, 'restart', () => {
|
|
||||||
this.restart();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.elements.buttons.rewind, 'click', event =>
|
on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
|
||||||
proxy(event, 'rewind', () => {
|
|
||||||
this.rewind();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.elements.buttons.forward, 'click', event =>
|
on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
|
||||||
proxy(event, 'forward', () => {
|
|
||||||
this.forward();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mute toggle
|
// Mute toggle
|
||||||
utils.on(this.elements.buttons.mute, 'click', event =>
|
on(
|
||||||
proxy(event, 'mute', () => {
|
this.player.elements.buttons.mute,
|
||||||
this.muted = !this.muted;
|
'click',
|
||||||
})
|
() => {
|
||||||
|
this.player.muted = !this.player.muted;
|
||||||
|
},
|
||||||
|
'mute',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
utils.on(this.elements.buttons.captions, 'click', event =>
|
on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
|
||||||
proxy(event, 'captions', () => {
|
|
||||||
this.toggleCaptions();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fullscreen toggle
|
// Fullscreen toggle
|
||||||
utils.on(this.elements.buttons.fullscreen, 'click', event =>
|
on(
|
||||||
proxy(event, 'fullscreen', () => {
|
this.player.elements.buttons.fullscreen,
|
||||||
this.toggleFullscreen();
|
'click',
|
||||||
})
|
() => {
|
||||||
|
this.player.fullscreen.toggle();
|
||||||
|
},
|
||||||
|
'fullscreen',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Picture-in-Picture
|
// Picture-in-Picture
|
||||||
utils.on(this.elements.buttons.pip, 'click', event =>
|
on(
|
||||||
proxy(event, 'pip', () => {
|
this.player.elements.buttons.pip,
|
||||||
this.pip = 'toggle';
|
'click',
|
||||||
})
|
() => {
|
||||||
|
this.player.pip = 'toggle';
|
||||||
|
},
|
||||||
|
'pip',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Airplay
|
// Airplay
|
||||||
utils.on(this.elements.buttons.airplay, 'click', event =>
|
on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
|
||||||
proxy(event, 'airplay', () => {
|
|
||||||
this.airplay();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu
|
||||||
utils.on(this.elements.buttons.settings, 'click', event => {
|
on(this.player.elements.buttons.settings, 'click', event => {
|
||||||
controls.toggleMenu.call(this, event);
|
controls.toggleMenu.call(this.player, event);
|
||||||
});
|
|
||||||
|
|
||||||
// Click anywhere closes menu
|
|
||||||
utils.on(document.documentElement, 'click', event => {
|
|
||||||
controls.toggleMenu.call(this, event);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu
|
||||||
utils.on(this.elements.settings.form, 'click', event => {
|
on(this.player.elements.settings.form, 'click', event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
// Settings menu items - use event delegation as items are added/removed
|
// Settings menu items - use event delegation as items are added/removed
|
||||||
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
|
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
|
||||||
proxy(event, 'language', () => {
|
proxy(
|
||||||
this.language = event.target.value;
|
event,
|
||||||
});
|
() => {
|
||||||
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
this.player.language = event.target.value;
|
||||||
proxy(event, 'quality', () => {
|
},
|
||||||
this.quality = event.target.value;
|
'language',
|
||||||
});
|
);
|
||||||
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
|
||||||
proxy(event, 'speed', () => {
|
proxy(
|
||||||
this.speed = parseFloat(event.target.value);
|
event,
|
||||||
});
|
() => {
|
||||||
|
this.player.quality = event.target.value;
|
||||||
|
},
|
||||||
|
'quality',
|
||||||
|
);
|
||||||
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
|
||||||
|
proxy(
|
||||||
|
event,
|
||||||
|
() => {
|
||||||
|
this.player.speed = parseFloat(event.target.value);
|
||||||
|
},
|
||||||
|
'speed',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
controls.showTab.call(this, event);
|
controls.showTab.call(this.player, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
utils.on(this.elements.inputs.seek, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'seek', () => {
|
this.player.elements.inputs.seek,
|
||||||
this.currentTime = event.target.value / event.target.max * this.duration;
|
inputEvent,
|
||||||
})
|
event => {
|
||||||
|
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
|
||||||
|
},
|
||||||
|
'seek',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Current time invert
|
// Current time invert
|
||||||
// Only if one time element is used for both currentTime and duration
|
// Only if one time element is used for both currentTime and duration
|
||||||
if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
|
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
|
||||||
utils.on(this.elements.display.currentTime, 'click', () => {
|
on(this.player.elements.display.currentTime, 'click', () => {
|
||||||
// Do nothing if we're at the start
|
// Do nothing if we're at the start
|
||||||
if (this.currentTime === 0) {
|
if (this.player.currentTime === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config.invertTime = !this.config.invertTime;
|
this.player.config.invertTime = !this.player.config.invertTime;
|
||||||
ui.timeUpdate.call(this);
|
ui.timeUpdate.call(this.player);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
utils.on(this.elements.inputs.volume, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'volume', () => {
|
this.player.elements.inputs.volume,
|
||||||
this.volume = event.target.value;
|
inputEvent,
|
||||||
})
|
event => {
|
||||||
|
this.player.volume = event.target.value;
|
||||||
|
},
|
||||||
|
'volume',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Polyfill for lower fill in <input type="range"> for webkit
|
// Polyfill for lower fill in <input type="range"> for webkit
|
||||||
if (browser.isWebkit) {
|
if (browser.isWebkit) {
|
||||||
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
|
on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
|
||||||
controls.updateRangeFill.call(this, event.target);
|
controls.updateRangeFill.call(this.player, event.target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek tooltip
|
// Seek tooltip
|
||||||
utils.on(this.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this, event));
|
on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
|
||||||
|
|
||||||
// Toggle controls visibility based on mouse movement
|
// Toggle controls visibility based on mouse movement
|
||||||
if (this.config.hideControls) {
|
if (this.player.config.hideControls) {
|
||||||
// Watch for cursor over controls so they don't hide when trying to interact
|
// Watch for cursor over controls so they don't hide when trying to interact
|
||||||
utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
|
on(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
||||||
this.elements.controls.hover = event.type === 'mouseenter';
|
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for cursor over controls so they don't hide when trying to interact
|
// Watch for cursor over controls so they don't hide when trying to interact
|
||||||
utils.on(this.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||||
this.elements.controls.pressed = [
|
this.player.elements.controls.pressed = [
|
||||||
'mousedown',
|
'mousedown',
|
||||||
'touchstart',
|
'touchstart',
|
||||||
].includes(event.type);
|
].includes(event.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus in/out on controls
|
// Focus in/out on controls
|
||||||
utils.on(this.elements.controls, 'focusin focusout', event => {
|
on(this.player.elements.controls, 'focusin focusout', event => {
|
||||||
this.toggleControls(event);
|
this.player.toggleControls(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse wheel for volume
|
// Mouse wheel for volume
|
||||||
utils.on(
|
on(
|
||||||
this.elements.inputs.volume,
|
this.player.elements.inputs.volume,
|
||||||
'wheel',
|
'wheel',
|
||||||
event =>
|
event => {
|
||||||
proxy(event, 'volume', () => {
|
// Detect "natural" scroll - suppored on OS X Safari only
|
||||||
// Detect "natural" scroll - suppored on OS X Safari only
|
// Other browsers on OS X will be inverted until support improves
|
||||||
// Other browsers on OS X will be inverted until support improves
|
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
const step = 1 / 50;
|
||||||
const step = 1 / 50;
|
let direction = 0;
|
||||||
let direction = 0;
|
|
||||||
|
|
||||||
// Scroll down (or up on natural) to decrease
|
// Scroll down (or up on natural) to decrease
|
||||||
if (event.deltaY < 0 || event.deltaX > 0) {
|
if (event.deltaY < 0 || event.deltaX > 0) {
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
this.decreaseVolume(step);
|
this.player.decreaseVolume(step);
|
||||||
direction = -1;
|
direction = -1;
|
||||||
} else {
|
} else {
|
||||||
this.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll up (or down on natural) to increase
|
// Scroll up (or down on natural) to increase
|
||||||
if (event.deltaY > 0 || event.deltaX < 0) {
|
if (event.deltaY > 0 || event.deltaX < 0) {
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
this.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
} else {
|
} else {
|
||||||
this.decreaseVolume(step);
|
this.player.decreaseVolume(step);
|
||||||
direction = -1;
|
direction = -1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't break page scrolling at max and min
|
// Don't break page scrolling at max and min
|
||||||
if ((direction === 1 && this.media.volume < 1) || (direction === -1 && this.media.volume > 0)) {
|
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
false
|
'volume',
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export default listeners;
|
// Reset on destroy
|
||||||
|
clear() {
|
||||||
|
this.global(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Listeners;
|
||||||
|
@ -46,7 +46,7 @@ const media = {
|
|||||||
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||||
|
|
||||||
// Add touch class
|
// Add touch class
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);
|
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the player wrapper
|
// Inject the player wrapper
|
||||||
@ -86,7 +86,7 @@ const media = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove child sources
|
// Remove child sources
|
||||||
Array.from(this.media.querySelectorAll('source')).forEach(utils.removeElement);
|
utils.removeElement(this.media.querySelectorAll('source'));
|
||||||
|
|
||||||
// Set blank video src attribute
|
// Set blank video src attribute
|
||||||
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
||||||
|
@ -7,22 +7,7 @@
|
|||||||
/* global google */
|
/* global google */
|
||||||
|
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
|
import i18n from '../i18n';
|
||||||
// Build the default tag URL
|
|
||||||
const getTagUrl = () => {
|
|
||||||
const params = {
|
|
||||||
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
|
|
||||||
AV_CHANNELID: '5a0458dc28a06145e4519d21',
|
|
||||||
AV_URL: '127.0.0.1:3000',
|
|
||||||
cb: 1,
|
|
||||||
AV_WIDTH: 640,
|
|
||||||
AV_HEIGHT: 480,
|
|
||||||
};
|
|
||||||
|
|
||||||
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
|
||||||
|
|
||||||
return `${base}?${utils.buildUrlParams(params)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Ads {
|
class Ads {
|
||||||
/**
|
/**
|
||||||
@ -32,29 +17,10 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.enabled = player.config.ads.enabled;
|
this.publisherId = player.config.ads.publisherId;
|
||||||
|
this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length;
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
|
|
||||||
// Check if a tag URL is provided.
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the Google IMA3 SDK is loaded
|
|
||||||
if (!utils.is.object(window.google)) {
|
|
||||||
utils.loadScript(player.config.urls.googleIMA.api, () => {
|
|
||||||
this.ready();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.ready();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ads instance ready.
|
|
||||||
*/
|
|
||||||
ready() {
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
displayContainer: null,
|
displayContainer: null,
|
||||||
@ -66,32 +32,77 @@ class Ads {
|
|||||||
this.safetyTimer = null;
|
this.safetyTimer = null;
|
||||||
this.countdownTimer = null;
|
this.countdownTimer = null;
|
||||||
|
|
||||||
// Set listeners on the Plyr instance
|
// Setup a promise to resolve when the IMA manager is ready
|
||||||
this.listeners();
|
this.managerPromise = new Promise((resolve, reject) => {
|
||||||
|
// The ad is loaded and ready
|
||||||
|
this.on('loaded', resolve);
|
||||||
|
|
||||||
|
// Ads failed
|
||||||
|
this.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the IMA SDK
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
if (this.enabled) {
|
||||||
|
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
||||||
|
if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) {
|
||||||
|
utils
|
||||||
|
.loadScript(this.player.config.urls.googleIMA.api)
|
||||||
|
.then(() => {
|
||||||
|
this.ready();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Script failed to load or is blocked
|
||||||
|
this.trigger('error', new Error('Google IMA SDK failed to load'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ads instance ready
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
// Start ticking our safety timer. If the whole advertisement
|
// Start ticking our safety timer. If the whole advertisement
|
||||||
// thing doesn't resolve within our set time; we bail
|
// thing doesn't resolve within our set time; we bail
|
||||||
this.startSafetyTimer(12000, 'ready()');
|
this.startSafetyTimer(12000, 'ready()');
|
||||||
|
|
||||||
// Setup a simple promise to resolve if the IMA loader is ready
|
|
||||||
this.loaderPromise = new Promise(resolve => {
|
|
||||||
this.on('ADS_LOADER_LOADED', () => resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup a promise to resolve if the IMA manager is ready
|
|
||||||
this.managerPromise = new Promise(resolve => {
|
|
||||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the safety timer
|
// Clear the safety timer
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise.then(() => {
|
||||||
this.clearSafetyTimer('onAdsManagerLoaded()');
|
this.clearSafetyTimer('onAdsManagerLoaded()');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set listeners on the Plyr instance
|
||||||
|
this.listeners();
|
||||||
|
|
||||||
// Setup the IMA SDK
|
// Setup the IMA SDK
|
||||||
this.setupIMA();
|
this.setupIMA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the default tag URL
|
||||||
|
get tagUrl() {
|
||||||
|
const params = {
|
||||||
|
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
|
||||||
|
AV_CHANNELID: '5a0458dc28a06145e4519d21',
|
||||||
|
AV_URL: location.hostname,
|
||||||
|
cb: Date.now(),
|
||||||
|
AV_WIDTH: 640,
|
||||||
|
AV_HEIGHT: 480,
|
||||||
|
AV_CDIM2: this.publisherId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||||
|
|
||||||
|
return `${base}?${utils.buildUrlParams(params)}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
||||||
* so here we define our ad container. This div is set up to render on top of the video player.
|
* so here we define our ad container. This div is set up to render on top of the video player.
|
||||||
@ -104,7 +115,6 @@ class Ads {
|
|||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = utils.createElement('div', {
|
this.elements.container = utils.createElement('div', {
|
||||||
class: this.player.config.classNames.ads,
|
class: this.player.config.classNames.ads,
|
||||||
hidden: '',
|
|
||||||
});
|
});
|
||||||
this.player.elements.container.appendChild(this.elements.container);
|
this.player.elements.container.appendChild(this.elements.container);
|
||||||
|
|
||||||
@ -138,7 +148,7 @@ class Ads {
|
|||||||
|
|
||||||
// Request video ads
|
// Request video ads
|
||||||
const request = new google.ima.AdsRequest();
|
const request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = getTagUrl();
|
request.adTagUrl = this.tagUrl;
|
||||||
|
|
||||||
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@ -151,8 +161,6 @@ class Ads {
|
|||||||
request.forceNonLinearFullSlot = false;
|
request.forceNonLinearFullSlot = false;
|
||||||
|
|
||||||
this.loader.requestAds(request);
|
this.loader.requestAds(request);
|
||||||
|
|
||||||
this.handleEventListeners('ADS_LOADER_LOADED');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.onAdError(e);
|
this.onAdError(e);
|
||||||
}
|
}
|
||||||
@ -164,25 +172,25 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
pollCountdown(start = false) {
|
pollCountdown(start = false) {
|
||||||
if (!start) {
|
if (!start) {
|
||||||
window.clearInterval(this.countdownTimer);
|
clearInterval(this.countdownTimer);
|
||||||
this.elements.container.removeAttribute('data-badge-text');
|
this.elements.container.removeAttribute('data-badge-text');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const time = utils.formatTime(this.manager.getRemainingTime());
|
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
|
||||||
const label = `${this.player.config.i18n.advertisment} - ${time}`;
|
const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
|
||||||
this.elements.container.setAttribute('data-badge-text', label);
|
this.elements.container.setAttribute('data-badge-text', label);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.countdownTimer = window.setInterval(update, 100);
|
this.countdownTimer = setInterval(update, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
||||||
* @param {Event} adsManagerLoadedEvent
|
* @param {Event} adsManagerLoadedEvent
|
||||||
*/
|
*/
|
||||||
onAdsManagerLoaded(adsManagerLoadedEvent) {
|
onAdsManagerLoaded(event) {
|
||||||
// Get the ads manager
|
// Get the ads manager
|
||||||
const settings = new google.ima.AdsRenderingSettings();
|
const settings = new google.ima.AdsRenderingSettings();
|
||||||
|
|
||||||
@ -192,14 +200,14 @@ class Ads {
|
|||||||
|
|
||||||
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
||||||
// so it can determine when to start the mid- and post-roll
|
// so it can determine when to start the mid- and post-roll
|
||||||
this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
this.manager = event.getAdsManager(this.player, settings);
|
||||||
|
|
||||||
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||||
this.cuePoints = this.manager.getCuePoints();
|
this.cuePoints = this.manager.getCuePoints();
|
||||||
|
|
||||||
// Add advertisement cue's within the time line if available
|
// Add advertisement cue's within the time line if available
|
||||||
this.cuePoints.forEach(cuePoint => {
|
this.cuePoints.forEach(cuePoint => {
|
||||||
if (cuePoint !== 0 && cuePoint !== -1) {
|
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
||||||
const seekElement = this.player.elements.progress;
|
const seekElement = this.player.elements.progress;
|
||||||
|
|
||||||
if (seekElement) {
|
if (seekElement) {
|
||||||
@ -231,7 +239,7 @@ class Ads {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Resolve our adsManager
|
// Resolve our adsManager
|
||||||
this.handleEventListeners('ADS_MANAGER_LOADED');
|
this.trigger('loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,17 +257,18 @@ class Ads {
|
|||||||
|
|
||||||
// Proxy event
|
// Proxy event
|
||||||
const dispatchEvent = type => {
|
const dispatchEvent = type => {
|
||||||
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
|
const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
|
||||||
|
utils.dispatchEvent.call(this.player, this.player.media, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case google.ima.AdEvent.Type.LOADED:
|
case google.ima.AdEvent.Type.LOADED:
|
||||||
// This is the first event sent for an ad - it is possible to determine whether the
|
// This is the first event sent for an ad - it is possible to determine whether the
|
||||||
// ad is a video ad or an overlay
|
// ad is a video ad or an overlay
|
||||||
this.handleEventListeners('LOADED');
|
this.trigger('loaded');
|
||||||
|
|
||||||
// Bubble event
|
// Bubble event
|
||||||
dispatchEvent('loaded');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
// Start countdown
|
// Start countdown
|
||||||
this.pollCountdown(true);
|
this.pollCountdown(true);
|
||||||
@ -277,10 +286,9 @@ class Ads {
|
|||||||
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
||||||
// All ads for the current videos are done. We can now request new advertisements
|
// All ads for the current videos are done. We can now request new advertisements
|
||||||
// in case the video is re-played
|
// in case the video is re-played
|
||||||
this.handleEventListeners('ALL_ADS_COMPLETED');
|
|
||||||
|
|
||||||
// Fire event
|
// Fire event
|
||||||
dispatchEvent('allcomplete');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
||||||
// So here we load a new video when all ads are done.
|
// So here we load a new video when all ads are done.
|
||||||
@ -312,9 +320,8 @@ class Ads {
|
|||||||
// This event indicates the ad has started - the video player can adjust the UI,
|
// This event indicates the ad has started - the video player can adjust the UI,
|
||||||
// for example display a pause button and remaining time. Fired when content should
|
// for example display a pause button and remaining time. Fired when content should
|
||||||
// be paused. This usually happens right before an ad is about to cover the content
|
// be paused. This usually happens right before an ad is about to cover the content
|
||||||
this.handleEventListeners('CONTENT_PAUSE_REQUESTED');
|
|
||||||
|
|
||||||
dispatchEvent('contentpause');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
this.pauseContent();
|
this.pauseContent();
|
||||||
|
|
||||||
@ -325,9 +332,8 @@ class Ads {
|
|||||||
// appropriate UI actions, such as removing the timer for remaining time detection.
|
// appropriate UI actions, such as removing the timer for remaining time detection.
|
||||||
// Fired when content should be resumed. This usually happens when an ad finishes
|
// Fired when content should be resumed. This usually happens when an ad finishes
|
||||||
// or collapses
|
// or collapses
|
||||||
this.handleEventListeners('CONTENT_RESUME_REQUESTED');
|
|
||||||
|
|
||||||
dispatchEvent('contentresume');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
this.pollCountdown();
|
this.pollCountdown();
|
||||||
|
|
||||||
@ -336,23 +342,11 @@ class Ads {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.STARTED:
|
case google.ima.AdEvent.Type.STARTED:
|
||||||
dispatchEvent('started');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.MIDPOINT:
|
case google.ima.AdEvent.Type.MIDPOINT:
|
||||||
dispatchEvent('midpoint');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.COMPLETE:
|
case google.ima.AdEvent.Type.COMPLETE:
|
||||||
dispatchEvent('complete');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.IMPRESSION:
|
case google.ima.AdEvent.Type.IMPRESSION:
|
||||||
dispatchEvent('impression');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CLICK:
|
case google.ima.AdEvent.Type.CLICK:
|
||||||
dispatchEvent('click');
|
dispatchEvent(event.type);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -366,7 +360,7 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
onAdError(event) {
|
onAdError(event) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.player.debug.log('Ads error', event);
|
this.player.debug.warn('Ads error', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -413,39 +407,41 @@ class Ads {
|
|||||||
const { container } = this.player.elements;
|
const { container } = this.player.elements;
|
||||||
|
|
||||||
if (!this.managerPromise) {
|
if (!this.managerPromise) {
|
||||||
return;
|
this.resumeContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play the requested advertisement whenever the adsManager is ready
|
// Play the requested advertisement whenever the adsManager is ready
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise
|
||||||
// Initialize the container. Must be done via a user action on mobile devices
|
.then(() => {
|
||||||
this.elements.displayContainer.initialize();
|
// Initialize the container. Must be done via a user action on mobile devices
|
||||||
|
this.elements.displayContainer.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
// Initialize the ads manager. Ad rules playlist will start at this time
|
// Initialize the ads manager. Ad rules playlist will start at this time
|
||||||
this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||||
|
|
||||||
// Call play to start showing the ad. Single video and overlay ads will
|
// Call play to start showing the ad. Single video and overlay ads will
|
||||||
// start at this time; the call will be ignored for ad rules
|
// start at this time; the call will be ignored for ad rules
|
||||||
this.manager.start();
|
this.manager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
} catch (adError) {
|
||||||
|
// An error may be thrown if there was a problem with the
|
||||||
|
// VAST response
|
||||||
|
this.onAdError(adError);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
this.initialized = true;
|
.catch(() => {});
|
||||||
} catch (adError) {
|
|
||||||
// An error may be thrown if there was a problem with the
|
|
||||||
// VAST response
|
|
||||||
this.onAdError(adError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume our video.
|
* Resume our video
|
||||||
*/
|
*/
|
||||||
resumeContent() {
|
resumeContent() {
|
||||||
// Hide our ad container
|
// Hide the advertisement container
|
||||||
utils.toggleHidden(this.elements.container, true);
|
this.elements.container.style.zIndex = '';
|
||||||
|
|
||||||
// Ad is stopped
|
// Ad is stopped
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
@ -460,8 +456,8 @@ class Ads {
|
|||||||
* Pause our video
|
* Pause our video
|
||||||
*/
|
*/
|
||||||
pauseContent() {
|
pauseContent() {
|
||||||
// Show our ad container.
|
// Show the advertisement container
|
||||||
utils.toggleHidden(this.elements.container, false);
|
this.elements.container.style.zIndex = 3;
|
||||||
|
|
||||||
// Ad is playing.
|
// Ad is playing.
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
@ -483,7 +479,7 @@ class Ads {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tell our instance that we're done for now
|
// Tell our instance that we're done for now
|
||||||
this.handleEventListeners('ERROR');
|
this.trigger('error');
|
||||||
|
|
||||||
// Re-create our adsManager
|
// Re-create our adsManager
|
||||||
this.loadAds();
|
this.loadAds();
|
||||||
@ -494,30 +490,38 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
loadAds() {
|
loadAds() {
|
||||||
// Tell our adsManager to go bye bye
|
// Tell our adsManager to go bye bye
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise
|
||||||
// Destroy our adsManager
|
.then(() => {
|
||||||
if (this.manager) {
|
// Destroy our adsManager
|
||||||
this.manager.destroy();
|
if (this.manager) {
|
||||||
}
|
this.manager.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
// Re-set our adsManager promises
|
// Re-set our adsManager promises
|
||||||
this.managerPromise = new Promise(resolve => {
|
this.managerPromise = new Promise(resolve => {
|
||||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
this.on('loaded', resolve);
|
||||||
this.player.debug.log(this.manager);
|
this.player.debug.log(this.manager);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now request some new advertisements
|
// Now request some new advertisements
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
});
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles callbacks after an ad event was invoked
|
* Handles callbacks after an ad event was invoked
|
||||||
* @param {string} event - Event type
|
* @param {string} event - Event type
|
||||||
*/
|
*/
|
||||||
handleEventListeners(event) {
|
trigger(event, ...args) {
|
||||||
if (utils.is.function(this.events[event])) {
|
const handlers = this.events[event];
|
||||||
this.events[event].call(this);
|
|
||||||
|
if (utils.is.array(handlers)) {
|
||||||
|
handlers.forEach(handler => {
|
||||||
|
if (utils.is.function(handler)) {
|
||||||
|
handler.apply(this, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,7 +532,12 @@ class Ads {
|
|||||||
* @return {Ads}
|
* @return {Ads}
|
||||||
*/
|
*/
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
this.events[event] = callback;
|
if (!utils.is.array(this.events[event])) {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events[event].push(callback);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +552,7 @@ class Ads {
|
|||||||
startSafetyTimer(time, from) {
|
startSafetyTimer(time, from) {
|
||||||
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||||
|
|
||||||
this.safetyTimer = window.setTimeout(() => {
|
this.safetyTimer = setTimeout(() => {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.clearSafetyTimer('startSafetyTimer()');
|
this.clearSafetyTimer('startSafetyTimer()');
|
||||||
}, time);
|
}, time);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import utils from './../utils';
|
import utils from './../utils';
|
||||||
import captions from './../captions';
|
import captions from './../captions';
|
||||||
|
import controls from './../controls';
|
||||||
import ui from './../ui';
|
import ui from './../ui';
|
||||||
|
|
||||||
const vimeo = {
|
const vimeo = {
|
||||||
@ -16,9 +17,14 @@ const vimeo = {
|
|||||||
|
|
||||||
// Load the API if not already
|
// Load the API if not already
|
||||||
if (!utils.is.object(window.Vimeo)) {
|
if (!utils.is.object(window.Vimeo)) {
|
||||||
utils.loadScript(this.config.urls.vimeo.api, () => {
|
utils
|
||||||
vimeo.ready.call(this);
|
.loadScript(this.config.urls.vimeo.api)
|
||||||
});
|
.then(() => {
|
||||||
|
vimeo.ready.call(this);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.debug.warn('Vimeo API failed to load', error);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
vimeo.ready.call(this);
|
vimeo.ready.call(this);
|
||||||
}
|
}
|
||||||
@ -29,7 +35,7 @@ const vimeo = {
|
|||||||
setAspectRatio(input) {
|
setAspectRatio(input) {
|
||||||
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
||||||
const padding = 100 / ratio[0] * ratio[1];
|
const padding = 100 / ratio[0] * ratio[1];
|
||||||
const height = 200;
|
const height = 240;
|
||||||
const offset = (height - padding) / (height / 50);
|
const offset = (height - padding) / (height / 50);
|
||||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||||
this.media.style.transform = `translateY(-${offset}%)`;
|
this.media.style.transform = `translateY(-${offset}%)`;
|
||||||
@ -51,7 +57,16 @@ const vimeo = {
|
|||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
};
|
};
|
||||||
const params = utils.buildUrlParams(options);
|
const params = utils.buildUrlParams(options);
|
||||||
const id = utils.parseVimeoId(player.media.getAttribute('src'));
|
|
||||||
|
// Get the source URL or ID
|
||||||
|
let source = player.media.getAttribute('src');
|
||||||
|
|
||||||
|
// Get from <div> if needed
|
||||||
|
if (utils.is.empty(source)) {
|
||||||
|
source = player.media.getAttribute(this.config.attributes.embed.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = utils.parseVimeoId(source);
|
||||||
|
|
||||||
// Build an iframe
|
// Build an iframe
|
||||||
const iframe = utils.createElement('iframe');
|
const iframe = utils.createElement('iframe');
|
||||||
@ -87,10 +102,8 @@ const vimeo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
player.media.stop = () => {
|
player.media.stop = () => {
|
||||||
player.embed.stop().then(() => {
|
player.pause();
|
||||||
player.media.paused = true;
|
player.currentTime = 0;
|
||||||
player.currentTime = 0;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Seeking
|
// Seeking
|
||||||
@ -127,10 +140,18 @@ const vimeo = {
|
|||||||
return speed;
|
return speed;
|
||||||
},
|
},
|
||||||
set(input) {
|
set(input) {
|
||||||
player.embed.setPlaybackRate(input).then(() => {
|
player.embed
|
||||||
speed = input;
|
.setPlaybackRate(input)
|
||||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
.then(() => {
|
||||||
});
|
speed = input;
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Hide menu item (and menu if empty)
|
||||||
|
if (error.name === 'Error') {
|
||||||
|
controls.setSpeedMenu.call(player, []);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -302,7 +323,7 @@ const vimeo = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Rebuild UI
|
// Rebuild UI
|
||||||
window.setTimeout(() => ui.build.call(player), 0);
|
setTimeout(() => ui.build.call(player), 0);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,9 @@ const youtube = {
|
|||||||
youtube.ready.call(this);
|
youtube.ready.call(this);
|
||||||
} else {
|
} else {
|
||||||
// Load the API
|
// Load the API
|
||||||
utils.loadScript(this.config.urls.youtube.api);
|
utils.loadScript(this.config.urls.youtube.api).catch(error => {
|
||||||
|
this.debug.warn('YouTube API failed to load', error);
|
||||||
|
});
|
||||||
|
|
||||||
// Setup callback for the API
|
// Setup callback for the API
|
||||||
// YouTube has it's own system of course...
|
// YouTube has it's own system of course...
|
||||||
@ -59,10 +61,10 @@ const youtube = {
|
|||||||
if (utils.is.string(key) && !utils.is.empty(key)) {
|
if (utils.is.string(key) && !utils.is.empty(key)) {
|
||||||
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
|
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
|
||||||
|
|
||||||
fetch(url)
|
utils
|
||||||
.then(response => (response.ok ? response.json() : null))
|
.fetch(url)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result !== null && utils.is.object(result)) {
|
if (utils.is.object(result)) {
|
||||||
this.config.title = result.items[0].snippet.title;
|
this.config.title = result.items[0].snippet.title;
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
}
|
}
|
||||||
@ -87,8 +89,16 @@ const youtube = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the source URL or ID
|
||||||
|
let source = player.media.getAttribute('src');
|
||||||
|
|
||||||
|
// Get from <div> if needed
|
||||||
|
if (utils.is.empty(source)) {
|
||||||
|
source = player.media.getAttribute(this.config.attributes.embed.id);
|
||||||
|
}
|
||||||
|
|
||||||
// Replace the <iframe> with a <div> due to YouTube API issues
|
// Replace the <iframe> with a <div> due to YouTube API issues
|
||||||
const videoId = utils.parseYouTubeId(player.media.getAttribute('src'));
|
const videoId = utils.parseYouTubeId(source);
|
||||||
const id = utils.generateId(player.provider);
|
const id = utils.generateId(player.provider);
|
||||||
const container = utils.createElement('div', { id });
|
const container = utils.createElement('div', { id });
|
||||||
player.media = utils.replaceElement(container, player.media);
|
player.media = utils.replaceElement(container, player.media);
|
||||||
@ -186,17 +196,14 @@ const youtube = {
|
|||||||
// Create a faux HTML5 API using the YouTube API
|
// Create a faux HTML5 API using the YouTube API
|
||||||
player.media.play = () => {
|
player.media.play = () => {
|
||||||
instance.playVideo();
|
instance.playVideo();
|
||||||
player.media.paused = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.pause = () => {
|
player.media.pause = () => {
|
||||||
instance.pauseVideo();
|
instance.pauseVideo();
|
||||||
player.media.paused = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.stop = () => {
|
player.media.stop = () => {
|
||||||
instance.stopVideo();
|
instance.stopVideo();
|
||||||
player.media.paused = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.duration = instance.getDuration();
|
player.media.duration = instance.getDuration();
|
||||||
@ -287,7 +294,8 @@ const youtube = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get available speeds
|
// Get available speeds
|
||||||
player.options.speed = instance.getAvailablePlaybackRates();
|
const options = instance.getAvailablePlaybackRates();
|
||||||
|
controls.setSpeedMenu.call(player, options);
|
||||||
|
|
||||||
// Set the tabindex to avoid focus entering iframe
|
// Set the tabindex to avoid focus entering iframe
|
||||||
if (player.supported.ui) {
|
if (player.supported.ui) {
|
||||||
@ -298,10 +306,10 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Setup buffering
|
// Setup buffering
|
||||||
player.timers.buffering = window.setInterval(() => {
|
player.timers.buffering = setInterval(() => {
|
||||||
// Get loaded % from YouTube
|
// Get loaded % from YouTube
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
|
||||||
@ -315,7 +323,7 @@ const youtube = {
|
|||||||
|
|
||||||
// Bail if we're at 100%
|
// Bail if we're at 100%
|
||||||
if (player.media.buffered === 1) {
|
if (player.media.buffered === 1) {
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||||
@ -323,14 +331,14 @@ const youtube = {
|
|||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
// Rebuild UI
|
// Rebuild UI
|
||||||
window.setTimeout(() => ui.build.call(player), 50);
|
setTimeout(() => ui.build.call(player), 50);
|
||||||
},
|
},
|
||||||
onStateChange(event) {
|
onStateChange(event) {
|
||||||
// Get the instance
|
// Get the instance
|
||||||
const instance = event.target;
|
const instance = event.target;
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.playing);
|
clearInterval(player.timers.playing);
|
||||||
|
|
||||||
// Handle events
|
// Handle events
|
||||||
// -1 Unstarted
|
// -1 Unstarted
|
||||||
@ -340,6 +348,16 @@ const youtube = {
|
|||||||
// 3 Buffering
|
// 3 Buffering
|
||||||
// 5 Video cued
|
// 5 Video cued
|
||||||
switch (event.data) {
|
switch (event.data) {
|
||||||
|
case -1:
|
||||||
|
// Update scrubber
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||||
|
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'progress');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case 0:
|
case 0:
|
||||||
player.media.paused = true;
|
player.media.paused = true;
|
||||||
|
|
||||||
@ -370,7 +388,7 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'playing');
|
utils.dispatchEvent.call(player, player.media, 'playing');
|
||||||
|
|
||||||
// Poll to get playback progress
|
// Poll to get playback progress
|
||||||
player.timers.playing = window.setInterval(() => {
|
player.timers.playing = setInterval(() => {
|
||||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
|
337
src/js/plyr.js
337
src/js/plyr.js
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.0.0-beta.8
|
// plyr.js v3.0.7
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -11,13 +11,13 @@ import support from './support';
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
|
|
||||||
import Console from './console';
|
import Console from './console';
|
||||||
|
import Fullscreen from './fullscreen';
|
||||||
|
import Listeners from './listeners';
|
||||||
import Storage from './storage';
|
import Storage from './storage';
|
||||||
import Ads from './plugins/ads';
|
import Ads from './plugins/ads';
|
||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import fullscreen from './fullscreen';
|
|
||||||
import listeners from './listeners';
|
|
||||||
import media from './media';
|
import media from './media';
|
||||||
import source from './source';
|
import source from './source';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
@ -26,12 +26,6 @@ import ui from './ui';
|
|||||||
// TODO: Use a WeakMap for private globals
|
// TODO: Use a WeakMap for private globals
|
||||||
// const globals = new WeakMap();
|
// const globals = new WeakMap();
|
||||||
|
|
||||||
// Globals
|
|
||||||
let scrollPosition = {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Plyr instance
|
// Plyr instance
|
||||||
class Plyr {
|
class Plyr {
|
||||||
constructor(target, options) {
|
constructor(target, options) {
|
||||||
@ -42,6 +36,9 @@ class Plyr {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.failed = false;
|
this.failed = false;
|
||||||
|
|
||||||
|
// Touch device
|
||||||
|
this.touch = support.touch;
|
||||||
|
|
||||||
// Set the media element
|
// Set the media element
|
||||||
this.media = target;
|
this.media = target;
|
||||||
|
|
||||||
@ -153,50 +150,53 @@ class Plyr {
|
|||||||
// Find the frame
|
// Find the frame
|
||||||
iframe = this.media.querySelector('iframe');
|
iframe = this.media.querySelector('iframe');
|
||||||
|
|
||||||
// <iframe> required
|
// <iframe> type
|
||||||
if (!utils.is.element(iframe)) {
|
if (utils.is.element(iframe)) {
|
||||||
this.debug.error('Setup failed: <iframe> is missing');
|
// Detect provider
|
||||||
return;
|
url = iframe.getAttribute('src');
|
||||||
|
this.provider = utils.getProviderByUrl(url);
|
||||||
|
|
||||||
|
// Rework elements
|
||||||
|
this.elements.container = this.media;
|
||||||
|
this.media = iframe;
|
||||||
|
|
||||||
|
// Reset classname
|
||||||
|
this.elements.container.className = '';
|
||||||
|
|
||||||
|
// Get attributes from URL and set config
|
||||||
|
params = utils.getUrlParams(url);
|
||||||
|
if (!utils.is.empty(params)) {
|
||||||
|
const truthy = [
|
||||||
|
'1',
|
||||||
|
'true',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (truthy.includes(params.autoplay)) {
|
||||||
|
this.config.autoplay = true;
|
||||||
|
}
|
||||||
|
if (truthy.includes(params.playsinline)) {
|
||||||
|
this.config.inline = true;
|
||||||
|
}
|
||||||
|
if (truthy.includes(params.loop)) {
|
||||||
|
this.config.loop.active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// <div> with attributes
|
||||||
|
this.provider = this.media.getAttribute(this.config.attributes.embed.provider);
|
||||||
|
|
||||||
|
// Remove attribute
|
||||||
|
this.media.removeAttribute(this.config.attributes.embed.provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio will come later for external providers
|
// Unsupported or missing provider
|
||||||
this.type = types.video;
|
|
||||||
|
|
||||||
// Detect provider
|
|
||||||
url = iframe.getAttribute('src');
|
|
||||||
this.provider = utils.getProviderByUrl(url);
|
|
||||||
|
|
||||||
// Get attributes from URL and set config
|
|
||||||
params = utils.getUrlParams(url);
|
|
||||||
if (!utils.is.empty(params)) {
|
|
||||||
const truthy = [
|
|
||||||
'1',
|
|
||||||
'true',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (truthy.includes(params.autoplay)) {
|
|
||||||
this.config.autoplay = true;
|
|
||||||
}
|
|
||||||
if (truthy.includes(params.playsinline)) {
|
|
||||||
this.config.inline = true;
|
|
||||||
}
|
|
||||||
if (truthy.includes(params.loop)) {
|
|
||||||
this.config.loop.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsupported provider
|
|
||||||
if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
|
if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
|
||||||
this.debug.error('Setup failed: Invalid provider');
|
this.debug.error('Setup failed: Invalid provider');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rework elements
|
// Audio will come later for external providers
|
||||||
this.elements.container = this.media;
|
this.type = types.video;
|
||||||
this.media = iframe;
|
|
||||||
|
|
||||||
// Reset classname
|
|
||||||
this.elements.container.className = '';
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -229,9 +229,6 @@ class Plyr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup local storage for user settings
|
|
||||||
this.storage = new Storage(this);
|
|
||||||
|
|
||||||
// Check for support again but with type
|
// Check for support again but with type
|
||||||
this.supported = support.check(this.type, this.provider, this.config.inline);
|
this.supported = support.check(this.type, this.provider, this.config.inline);
|
||||||
|
|
||||||
@ -241,6 +238,12 @@ class Plyr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create listeners
|
||||||
|
this.listeners = new Listeners(this);
|
||||||
|
|
||||||
|
// Setup local storage for user settings
|
||||||
|
this.storage = new Storage(this);
|
||||||
|
|
||||||
// Store reference
|
// Store reference
|
||||||
this.media.plyr = this;
|
this.media.plyr = this;
|
||||||
|
|
||||||
@ -253,9 +256,6 @@ class Plyr {
|
|||||||
// Allow focus to be captured
|
// Allow focus to be captured
|
||||||
this.elements.container.setAttribute('tabindex', 0);
|
this.elements.container.setAttribute('tabindex', 0);
|
||||||
|
|
||||||
// Global listeners
|
|
||||||
listeners.global.call(this);
|
|
||||||
|
|
||||||
// Add style hook
|
// Add style hook
|
||||||
ui.addStyleHook.call(this);
|
ui.addStyleHook.call(this);
|
||||||
|
|
||||||
@ -275,6 +275,15 @@ class Plyr {
|
|||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Container listeners
|
||||||
|
this.listeners.container();
|
||||||
|
|
||||||
|
// Global listeners
|
||||||
|
this.listeners.global();
|
||||||
|
|
||||||
|
// Setup fullscreen
|
||||||
|
this.fullscreen = new Fullscreen(this);
|
||||||
|
|
||||||
// Setup ads if provided
|
// Setup ads if provided
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
}
|
}
|
||||||
@ -287,32 +296,35 @@ class Plyr {
|
|||||||
* Types and provider helpers
|
* Types and provider helpers
|
||||||
*/
|
*/
|
||||||
get isHTML5() {
|
get isHTML5() {
|
||||||
return this.provider === providers.html5;
|
return Boolean(this.provider === providers.html5);
|
||||||
}
|
}
|
||||||
get isEmbed() {
|
get isEmbed() {
|
||||||
return this.isYouTube || this.isVimeo;
|
return Boolean(this.isYouTube || this.isVimeo);
|
||||||
}
|
}
|
||||||
get isYouTube() {
|
get isYouTube() {
|
||||||
return this.provider === providers.youtube;
|
return Boolean(this.provider === providers.youtube);
|
||||||
}
|
}
|
||||||
get isVimeo() {
|
get isVimeo() {
|
||||||
return this.provider === providers.vimeo;
|
return Boolean(this.provider === providers.vimeo);
|
||||||
}
|
}
|
||||||
get isVideo() {
|
get isVideo() {
|
||||||
return this.type === types.video;
|
return Boolean(this.type === types.video);
|
||||||
}
|
}
|
||||||
get isAudio() {
|
get isAudio() {
|
||||||
return this.type === types.audio;
|
return Boolean(this.type === types.audio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the media, or play the advertisement
|
* Play the media, or play the advertisement (if they are not blocked)
|
||||||
*/
|
*/
|
||||||
play() {
|
play() {
|
||||||
// Play the ad if setup
|
if (!utils.is.function(this.media.play)) {
|
||||||
// TODO: Fix the occasional play of the video before the Ad fires?
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ads are enabled, wait for them first
|
||||||
if (this.ads.enabled && !this.ads.initialized) {
|
if (this.ads.enabled && !this.ads.initialized) {
|
||||||
this.ads.play();
|
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the promise (for HTML5)
|
// Return the promise (for HTML5)
|
||||||
@ -323,7 +335,7 @@ class Plyr {
|
|||||||
* Pause the media
|
* Pause the media
|
||||||
*/
|
*/
|
||||||
pause() {
|
pause() {
|
||||||
if (!this.playing) {
|
if (!this.playing || !utils.is.function(this.media.pause)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,21 +346,21 @@ class Plyr {
|
|||||||
* Get paused state
|
* Get paused state
|
||||||
*/
|
*/
|
||||||
get paused() {
|
get paused() {
|
||||||
return this.media.paused;
|
return Boolean(this.media.paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get playing state
|
* Get playing state
|
||||||
*/
|
*/
|
||||||
get playing() {
|
get playing() {
|
||||||
return !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true);
|
return Boolean(!this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ended state
|
* Get ended state
|
||||||
*/
|
*/
|
||||||
get ended() {
|
get ended() {
|
||||||
return this.media.ended;
|
return Boolean(this.media.ended);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -370,8 +382,11 @@ class Plyr {
|
|||||||
* Stop playback
|
* Stop playback
|
||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
this.restart();
|
if (this.isHTML5) {
|
||||||
this.pause();
|
this.media.load();
|
||||||
|
} else {
|
||||||
|
this.media.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -416,7 +431,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set
|
// Set
|
||||||
this.media.currentTime = targetTime.toFixed(4);
|
this.media.currentTime = parseFloat(targetTime.toFixed(4));
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
||||||
@ -429,11 +444,32 @@ class Plyr {
|
|||||||
return Number(this.media.currentTime);
|
return Number(this.media.currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get buffered
|
||||||
|
*/
|
||||||
|
get buffered() {
|
||||||
|
const { buffered } = this.media;
|
||||||
|
|
||||||
|
// YouTube / Vimeo return a float between 0-1
|
||||||
|
if (utils.is.number(buffered)) {
|
||||||
|
return buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML5
|
||||||
|
// TODO: Handle buffered chunks of the media
|
||||||
|
// (i.e. seek to another section buffers only that section)
|
||||||
|
if (buffered && buffered.length && this.duration > 0) {
|
||||||
|
return buffered.end(0) / this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get seeking status
|
* Get seeking status
|
||||||
*/
|
*/
|
||||||
get seeking() {
|
get seeking() {
|
||||||
return this.media.seeking;
|
return Boolean(this.media.seeking);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,7 +480,7 @@ class Plyr {
|
|||||||
const fauxDuration = parseInt(this.config.duration, 10);
|
const fauxDuration = parseInt(this.config.duration, 10);
|
||||||
|
|
||||||
// True duration
|
// True duration
|
||||||
const realDuration = Number(this.media.duration);
|
const realDuration = this.media ? Number(this.media.duration) : 0;
|
||||||
|
|
||||||
// If custom duration is funky, use regular duration
|
// If custom duration is funky, use regular duration
|
||||||
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
||||||
@ -498,7 +534,7 @@ class Plyr {
|
|||||||
* Get the current player volume
|
* Get the current player volume
|
||||||
*/
|
*/
|
||||||
get volume() {
|
get volume() {
|
||||||
return this.media.volume;
|
return Number(this.media.volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -547,7 +583,7 @@ class Plyr {
|
|||||||
* Get current muted state
|
* Get current muted state
|
||||||
*/
|
*/
|
||||||
get muted() {
|
get muted() {
|
||||||
return this.media.muted;
|
return Boolean(this.media.muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -559,13 +595,21 @@ class Plyr {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isAudio) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Get audio tracks
|
// Get audio tracks
|
||||||
return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
|
return (
|
||||||
|
Boolean(this.media.mozHasAudio) ||
|
||||||
|
Boolean(this.media.webkitAudioDecodedByteCount) ||
|
||||||
|
Boolean(this.media.audioTracks && this.media.audioTracks.length)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback speed
|
* Set playback speed
|
||||||
* @param {decimal} speed - the speed of playback (0.5-2.0)
|
* @param {number} speed - the speed of playback (0.5-2.0)
|
||||||
*/
|
*/
|
||||||
set speed(input) {
|
set speed(input) {
|
||||||
let speed = null;
|
let speed = null;
|
||||||
@ -606,7 +650,7 @@ class Plyr {
|
|||||||
* Get current playback speed
|
* Get current playback speed
|
||||||
*/
|
*/
|
||||||
get speed() {
|
get speed() {
|
||||||
return this.media.playbackRate;
|
return Number(this.media.playbackRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -706,7 +750,7 @@ class Plyr {
|
|||||||
* Get current loop state
|
* Get current loop state
|
||||||
*/
|
*/
|
||||||
get loop() {
|
get loop() {
|
||||||
return this.media.loop;
|
return Boolean(this.media.loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -763,7 +807,7 @@ class Plyr {
|
|||||||
* Get the current autoplay state
|
* Get the current autoplay state
|
||||||
*/
|
*/
|
||||||
get autoplay() {
|
get autoplay() {
|
||||||
return this.config.autoplay;
|
return Boolean(this.config.autoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -843,62 +887,6 @@ class Plyr {
|
|||||||
return this.captions.language;
|
return this.captions.language;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle fullscreen playback
|
|
||||||
* Requires user input event
|
|
||||||
* @param {event} event
|
|
||||||
*/
|
|
||||||
toggleFullscreen(event) {
|
|
||||||
// Video only
|
|
||||||
if (this.isAudio) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for native support
|
|
||||||
if (fullscreen.enabled) {
|
|
||||||
if (utils.is.event(event) && event.type === fullscreen.eventType) {
|
|
||||||
// If it's a fullscreen change event, update the state
|
|
||||||
this.fullscreen.active = fullscreen.isFullScreen(this.elements.container);
|
|
||||||
} else {
|
|
||||||
// Else it's a user request to enter or exit
|
|
||||||
if (!this.fullscreen.active) {
|
|
||||||
fullscreen.requestFullScreen(this.elements.container);
|
|
||||||
} else {
|
|
||||||
fullscreen.cancelFullScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, it's a simple toggle
|
|
||||||
this.fullscreen.active = !this.fullscreen.active;
|
|
||||||
|
|
||||||
// Add class hook
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.fallback, this.fullscreen.active);
|
|
||||||
|
|
||||||
// Make sure we don't lose scroll position
|
|
||||||
if (this.fullscreen.active) {
|
|
||||||
scrollPosition = {
|
|
||||||
x: window.pageXOffset || 0,
|
|
||||||
y: window.pageYOffset || 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
window.scrollTo(scrollPosition.x, scrollPosition.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind/unbind escape key
|
|
||||||
document.body.style.overflow = this.fullscreen.active ? 'hidden' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set button state
|
|
||||||
if (utils.is.element(this.elements.buttons.fullscreen)) {
|
|
||||||
utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger an event
|
|
||||||
utils.dispatchEvent.call(this, this.media, this.fullscreen.active ? 'enterfullscreen' : 'exitfullscreen');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle picture-in-picture playback on WebKit/MacOS
|
* Toggle picture-in-picture playback on WebKit/MacOS
|
||||||
* TODO: update player with state, support, enabled
|
* TODO: update player with state, support, enabled
|
||||||
@ -969,26 +957,32 @@ class Plyr {
|
|||||||
// Is the enter fullscreen event
|
// Is the enter fullscreen event
|
||||||
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
||||||
|
|
||||||
// Whether to show controls
|
// Events that show the controls
|
||||||
show = [
|
const showEvents = [
|
||||||
'mouseenter',
|
|
||||||
'mousemove',
|
|
||||||
'touchstart',
|
'touchstart',
|
||||||
'touchmove',
|
'touchmove',
|
||||||
'focusin',
|
'mouseenter',
|
||||||
].includes(toggle.type);
|
|
||||||
|
|
||||||
// Delay hiding on move events
|
|
||||||
if ([
|
|
||||||
'mousemove',
|
'mousemove',
|
||||||
|
'focusin',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Events that delay hiding
|
||||||
|
const delayEvents = [
|
||||||
'touchmove',
|
'touchmove',
|
||||||
'touchend',
|
'touchend',
|
||||||
].includes(toggle.type)) {
|
'mousemove',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Whether to show controls
|
||||||
|
show = showEvents.includes(toggle.type);
|
||||||
|
|
||||||
|
// Delay hiding on move events
|
||||||
|
if (delayEvents.includes(toggle.type)) {
|
||||||
delay = 2000;
|
delay = 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay a little more for keyboard users
|
// Delay a little more for keyboard users
|
||||||
if (toggle.type === 'focusin') {
|
if (!this.touch && toggle.type === 'focusin') {
|
||||||
delay = 3000;
|
delay = 3000;
|
||||||
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
|
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
|
||||||
}
|
}
|
||||||
@ -998,7 +992,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear timer on every call
|
// Clear timer on every call
|
||||||
window.clearTimeout(this.timers.controls);
|
clearTimeout(this.timers.controls);
|
||||||
|
|
||||||
// If the mouse is not over the controls, set a timeout to hide them
|
// If the mouse is not over the controls, set a timeout to hide them
|
||||||
if (show || this.paused || this.loading) {
|
if (show || this.paused || this.loading) {
|
||||||
@ -1016,7 +1010,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delay for hiding on touch
|
// Delay for hiding on touch
|
||||||
if (support.touch) {
|
if (this.touch) {
|
||||||
delay = 3000;
|
delay = 3000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1024,15 +1018,7 @@ class Plyr {
|
|||||||
// If toggle is false or if we're playing (regardless of toggle),
|
// If toggle is false or if we're playing (regardless of toggle),
|
||||||
// then set the timer to hide the controls
|
// then set the timer to hide the controls
|
||||||
if (!show || this.playing) {
|
if (!show || this.playing) {
|
||||||
this.timers.controls = window.setTimeout(() => {
|
this.timers.controls = setTimeout(() => {
|
||||||
/* this.debug.warn({
|
|
||||||
pressed: this.elements.controls.pressed,
|
|
||||||
hover: this.elements.controls.pressed,
|
|
||||||
playing: this.playing,
|
|
||||||
paused: this.paused,
|
|
||||||
loading: this.loading,
|
|
||||||
}); */
|
|
||||||
|
|
||||||
// If the mouse is over the controls (and not entering fullscreen), bail
|
// If the mouse is over the controls (and not entering fullscreen), bail
|
||||||
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
||||||
return;
|
return;
|
||||||
@ -1084,6 +1070,10 @@ class Plyr {
|
|||||||
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
||||||
*/
|
*/
|
||||||
destroy(callback, soft = false) {
|
destroy(callback, soft = false) {
|
||||||
|
if (!this.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const done = () => {
|
const done = () => {
|
||||||
// Reset overflow (incase destroyed while in fullscreen)
|
// Reset overflow (incase destroyed while in fullscreen)
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@ -1094,12 +1084,8 @@ class Plyr {
|
|||||||
// If it's a soft destroy, make minimal changes
|
// If it's a soft destroy, make minimal changes
|
||||||
if (soft) {
|
if (soft) {
|
||||||
if (Object.keys(this.elements).length) {
|
if (Object.keys(this.elements).length) {
|
||||||
// Remove buttons
|
// Remove elements
|
||||||
if (this.elements.buttons && this.elements.buttons.play) {
|
utils.removeElement(this.elements.buttons.play);
|
||||||
Array.from(this.elements.buttons.play).forEach(button => utils.removeElement(button));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove others
|
|
||||||
utils.removeElement(this.elements.captions);
|
utils.removeElement(this.elements.captions);
|
||||||
utils.removeElement(this.elements.controls);
|
utils.removeElement(this.elements.controls);
|
||||||
utils.removeElement(this.elements.wrapper);
|
utils.removeElement(this.elements.wrapper);
|
||||||
@ -1116,6 +1102,9 @@ class Plyr {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Unbind listeners
|
||||||
|
this.listeners.clear();
|
||||||
|
|
||||||
// Replace the container with the original element provided
|
// Replace the container with the original element provided
|
||||||
utils.replaceElement(this.elements.original, this.elements.container);
|
utils.replaceElement(this.elements.original, this.elements.container);
|
||||||
|
|
||||||
@ -1127,15 +1116,27 @@ class Plyr {
|
|||||||
callback.call(this.elements.original);
|
callback.call(this.elements.original);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear for GC
|
// Reset state
|
||||||
this.elements = null;
|
this.ready = false;
|
||||||
|
|
||||||
|
// Clear for garbage collection
|
||||||
|
setTimeout(() => {
|
||||||
|
this.elements = null;
|
||||||
|
this.media = null;
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stop playback
|
||||||
|
this.stop();
|
||||||
|
|
||||||
// Type specific stuff
|
// Type specific stuff
|
||||||
switch (`${this.provider}:${this.type}`) {
|
switch (`${this.provider}:${this.type}`) {
|
||||||
case 'html5:video':
|
case 'html5:video':
|
||||||
case 'html5:audio':
|
case 'html5:audio':
|
||||||
|
// Clear timeout
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
// Restore native video controls
|
// Restore native video controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
@ -1146,11 +1147,11 @@ class Plyr {
|
|||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
// Clear timers
|
// Clear timers
|
||||||
window.clearInterval(this.timers.buffering);
|
clearInterval(this.timers.buffering);
|
||||||
window.clearInterval(this.timers.playing);
|
clearInterval(this.timers.playing);
|
||||||
|
|
||||||
// Destroy YouTube API
|
// Destroy YouTube API
|
||||||
if (this.embed !== null) {
|
if (this.embed !== null && utils.is.function(this.embed.destroy)) {
|
||||||
this.embed.destroy();
|
this.embed.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1167,7 +1168,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vimeo does not always return
|
// Vimeo does not always return
|
||||||
window.setTimeout(done, 200);
|
setTimeout(done, 200);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
14
src/js/plyr.polyfilled.js
Normal file
14
src/js/plyr.polyfilled.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Plyr Polyfilled Build
|
||||||
|
// plyr.js v3.0.7
|
||||||
|
// https://github.com/sampotts/plyr
|
||||||
|
// License: The MIT License (MIT)
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
import 'babel-polyfill';
|
||||||
|
|
||||||
|
import 'custom-event-polyfill';
|
||||||
|
|
||||||
|
import Plyr from './plyr';
|
||||||
|
|
||||||
|
export default Plyr;
|
@ -136,6 +136,9 @@ const source = {
|
|||||||
// Setup interface
|
// Setup interface
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the fullscreen support
|
||||||
|
this.fullscreen.update();
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
@ -12,17 +12,18 @@ class Storage {
|
|||||||
|
|
||||||
// Check for actual support (see if we can use it)
|
// Check for actual support (see if we can use it)
|
||||||
static get supported() {
|
static get supported() {
|
||||||
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
|
|
||||||
try {
|
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.setItem(test, test);
|
||||||
window.localStorage.removeItem(test);
|
window.localStorage.removeItem(test);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
@ -30,9 +31,13 @@ class Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
|
if (!Storage.supported) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const store = window.localStorage.getItem(this.key);
|
const store = window.localStorage.getItem(this.key);
|
||||||
|
|
||||||
if (!Storage.supported || utils.is.empty(store)) {
|
if (utils.is.empty(store)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +30,9 @@ const support = {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
api = true;
|
|
||||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'vimeo:video':
|
case 'vimeo:video':
|
||||||
api = true;
|
api = true;
|
||||||
ui = support.rangeInput && !browser.isIPhone;
|
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -147,7 +143,7 @@ const support = {
|
|||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
// Remember a device can be moust + touch enabled
|
// 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
|
||||||
|
48
src/js/ui.js
48
src/js/ui.js
@ -5,8 +5,7 @@
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import fullscreen from './fullscreen';
|
import i18n from './i18n';
|
||||||
import listeners from './listeners';
|
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
@ -26,19 +25,13 @@ const ui = {
|
|||||||
// 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?
|
||||||
listeners.media.call(this);
|
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}`);
|
||||||
|
|
||||||
// Remove controls
|
|
||||||
utils.removeElement.call(this, 'controls');
|
|
||||||
|
|
||||||
// Remove large play
|
|
||||||
utils.removeElement.call(this, 'buttons.play');
|
|
||||||
|
|
||||||
// Restore native controls
|
// Restore native controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
@ -52,7 +45,7 @@ const ui = {
|
|||||||
controls.inject.call(this);
|
controls.inject.call(this);
|
||||||
|
|
||||||
// Re-attach control listeners
|
// Re-attach control listeners
|
||||||
listeners.controls.call(this);
|
this.listeners.controls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no controls, bail
|
// If there's no controls, bail
|
||||||
@ -63,9 +56,6 @@ const ui = {
|
|||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Setup fullscreen
|
|
||||||
fullscreen.setup.call(this);
|
|
||||||
|
|
||||||
// Captions
|
// Captions
|
||||||
captions.setup.call(this);
|
captions.setup.call(this);
|
||||||
|
|
||||||
@ -94,7 +84,9 @@ const ui = {
|
|||||||
this.ready = true;
|
this.ready = true;
|
||||||
|
|
||||||
// Ready event at end of execution stack
|
// Ready event at end of execution stack
|
||||||
utils.dispatchEvent.call(this, this.media, 'ready');
|
setTimeout(() => {
|
||||||
|
utils.dispatchEvent.call(this, this.media, 'ready');
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// Set the title
|
// Set the title
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
@ -103,7 +95,7 @@ const ui = {
|
|||||||
// 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 = this.config.i18n.play;
|
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 (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
|
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
|
||||||
@ -132,7 +124,7 @@ const ui = {
|
|||||||
// Default to media type
|
// Default to media type
|
||||||
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
||||||
|
|
||||||
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title));
|
iframe.setAttribute('title', i18n.get('frameTitle', this.config));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -142,10 +134,8 @@ const ui = {
|
|||||||
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
||||||
|
|
||||||
// Set aria state
|
// Set ARIA state
|
||||||
if (utils.is.nodeList(this.elements.buttons.play)) {
|
utils.toggleState(this.elements.buttons.play, this.playing);
|
||||||
Array.from(this.elements.buttons.play).forEach(button => utils.toggleState(button, this.playing));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle controls
|
// Toggle controls
|
||||||
this.toggleControls(!this.playing);
|
this.toggleControls(!this.playing);
|
||||||
@ -266,21 +256,7 @@ const ui = {
|
|||||||
// Check buffer status
|
// Check buffer status
|
||||||
case 'playing':
|
case 'playing':
|
||||||
case 'progress':
|
case 'progress':
|
||||||
value = (() => {
|
ui.setProgress.call(this, this.elements.display.buffer, this.buffered * 100);
|
||||||
const { buffered } = this.media;
|
|
||||||
|
|
||||||
if (buffered && buffered.length) {
|
|
||||||
// HTML5
|
|
||||||
return utils.getPercentage(buffered.end(0), this.duration);
|
|
||||||
} else if (utils.is.number(buffered)) {
|
|
||||||
// YouTube returns between 0 and 1
|
|
||||||
return buffered * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
})();
|
|
||||||
|
|
||||||
ui.setProgress.call(this, this.elements.display.buffer, value);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
274
src/js/utils.js
274
src/js/utils.js
@ -2,6 +2,8 @@
|
|||||||
// Plyr utils
|
// Plyr utils
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import loadjs from 'loadjs';
|
||||||
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
import { providers } from './types';
|
import { providers } from './types';
|
||||||
|
|
||||||
@ -9,7 +11,7 @@ const utils = {
|
|||||||
// Check variable types
|
// Check variable types
|
||||||
is: {
|
is: {
|
||||||
plyr(input) {
|
plyr(input) {
|
||||||
return this.instanceof(input, Plyr);
|
return this.instanceof(input, window.Plyr);
|
||||||
},
|
},
|
||||||
object(input) {
|
object(input) {
|
||||||
return this.getConstructor(input) === Object;
|
return this.getConstructor(input) === Object;
|
||||||
@ -81,42 +83,54 @@ const utils = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Fetch wrapper
|
||||||
|
// Using XHR to avoid issues with older browsers
|
||||||
|
fetch(url, responseType = 'text') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
// Check for CORS support
|
||||||
|
if (!('withCredentials' in request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addEventListener('load', () => {
|
||||||
|
if (responseType === 'text') {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(request.responseText));
|
||||||
|
} catch (e) {
|
||||||
|
resolve(request.responseText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve(request.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
request.addEventListener('error', () => {
|
||||||
|
throw new Error(request.statusText);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.open('GET', url, true);
|
||||||
|
|
||||||
|
// Set the required response type
|
||||||
|
request.responseType = responseType;
|
||||||
|
|
||||||
|
request.send();
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Load an external script
|
// Load an external script
|
||||||
loadScript(url, callback) {
|
loadScript(url) {
|
||||||
const current = document.querySelector(`script[src="${url}"]`);
|
return new Promise((resolve, reject) => {
|
||||||
|
loadjs(url, {
|
||||||
// Check script is not already referenced, if so wait for load
|
success: resolve,
|
||||||
if (current !== null) {
|
error: reject,
|
||||||
current.callbacks = current.callbacks || [];
|
});
|
||||||
current.callbacks.push(callback);
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element
|
|
||||||
const element = document.createElement('script');
|
|
||||||
|
|
||||||
// Callback queue
|
|
||||||
element.callbacks = element.callbacks || [];
|
|
||||||
element.callbacks.push(callback);
|
|
||||||
|
|
||||||
// Bind callback
|
|
||||||
if (utils.is.function(callback)) {
|
|
||||||
element.addEventListener(
|
|
||||||
'load',
|
|
||||||
event => {
|
|
||||||
element.callbacks.forEach(cb => cb.call(null, event));
|
|
||||||
element.callbacks = null;
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the URL after binding callback
|
|
||||||
element.src = url;
|
|
||||||
|
|
||||||
// Inject
|
|
||||||
const first = document.getElementsByTagName('script')[0];
|
|
||||||
first.parentNode.insertBefore(element, first);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load an external SVG sprite
|
// Load an external SVG sprite
|
||||||
@ -129,7 +143,14 @@ const utils = {
|
|||||||
const hasId = utils.is.string(id);
|
const hasId = utils.is.string(id);
|
||||||
let isCached = false;
|
let isCached = false;
|
||||||
|
|
||||||
function updateSprite(data) {
|
const exists = () => document.querySelectorAll(`#${id}`).length;
|
||||||
|
|
||||||
|
function injectSprite(data) {
|
||||||
|
// Check again incase of race condition
|
||||||
|
if (hasId && exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Inject content
|
// Inject content
|
||||||
this.innerHTML = data;
|
this.innerHTML = data;
|
||||||
|
|
||||||
@ -137,8 +158,8 @@ const utils = {
|
|||||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
document.body.insertBefore(this, document.body.childNodes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only load once
|
// Only load once if ID set
|
||||||
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
|
if (!hasId || !exists()) {
|
||||||
// Create container
|
// Create container
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
utils.toggleHidden(container, true);
|
utils.toggleHidden(container, true);
|
||||||
@ -154,16 +175,16 @@ const utils = {
|
|||||||
|
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
const data = JSON.parse(cached);
|
const data = JSON.parse(cached);
|
||||||
updateSprite.call(container, data.content);
|
injectSprite.call(container, data.content);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the sprite
|
// Get the sprite
|
||||||
fetch(url)
|
utils
|
||||||
.then(response => (response.ok ? response.text() : null))
|
.fetch(url)
|
||||||
.then(text => {
|
.then(result => {
|
||||||
if (text === null) {
|
if (utils.is.empty(result)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,12 +192,12 @@ const utils = {
|
|||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
prefix + id,
|
prefix + id,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: text,
|
content: result,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSprite.call(container, text);
|
injectSprite.call(container, result);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
@ -187,15 +208,6 @@ const utils = {
|
|||||||
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Determine if we're in an iframe
|
|
||||||
inFrame() {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Wrap an element
|
// Wrap an element
|
||||||
wrap(elements, wrapper) {
|
wrap(elements, wrapper) {
|
||||||
// Convert `elements` to an array, if necessary.
|
// Convert `elements` to an array, if necessary.
|
||||||
@ -260,12 +272,15 @@ const utils = {
|
|||||||
// Remove an element
|
// Remove an element
|
||||||
removeElement(element) {
|
removeElement(element) {
|
||||||
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
|
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
|
||||||
return null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utils.is.nodeList(element) || utils.is.array(element)) {
|
||||||
|
Array.from(element).forEach(utils.removeElement);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.parentNode.removeChild(element);
|
element.parentNode.removeChild(element);
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
@ -295,8 +310,11 @@ const utils = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(attributes).forEach(key => {
|
Object.entries(attributes).forEach(([
|
||||||
element.setAttribute(key, attributes[key]);
|
key,
|
||||||
|
value,
|
||||||
|
]) => {
|
||||||
|
element.setAttribute(key, value);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -423,7 +441,7 @@ const utils = {
|
|||||||
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
||||||
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
||||||
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
||||||
forward: utils.getElement.call(this, this.config.selectors.buttons.forward),
|
fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
|
||||||
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
||||||
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
||||||
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
||||||
@ -479,46 +497,51 @@ const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Trap focus inside container
|
// Trap focus inside container
|
||||||
trapFocus() {
|
trapFocus(element = null, toggle = false) {
|
||||||
|
if (!utils.is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||||
const first = focusable[0];
|
const first = focusable[0];
|
||||||
const last = focusable[focusable.length - 1];
|
const last = focusable[focusable.length - 1];
|
||||||
|
|
||||||
utils.on(
|
const trap = event => {
|
||||||
this.elements.container,
|
// Bail if not tab key or not fullscreen
|
||||||
'keydown',
|
if (event.key !== 'Tab' || event.keyCode !== 9) {
|
||||||
event => {
|
return;
|
||||||
// Bail if not tab key or not fullscreen
|
}
|
||||||
if (event.key !== 'Tab' || event.keyCode !== 9 || !this.fullscreen.active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current focused element
|
// Get the current focused element
|
||||||
const focused = utils.getFocusElement();
|
const focused = utils.getFocusElement();
|
||||||
|
|
||||||
if (focused === last && !event.shiftKey) {
|
if (focused === last && !event.shiftKey) {
|
||||||
// Move focus to first element that can be tabbed if Shift isn't used
|
// Move focus to first element that can be tabbed if Shift isn't used
|
||||||
first.focus();
|
first.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
} else if (focused === first && event.shiftKey) {
|
} else if (focused === first && event.shiftKey) {
|
||||||
// Move focus to last element that can be tabbed if Shift is used
|
// Move focus to last element that can be tabbed if Shift is used
|
||||||
last.focus();
|
last.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
false,
|
|
||||||
);
|
if (toggle) {
|
||||||
|
utils.on(this.elements.container, 'keydown', trap, false);
|
||||||
|
} else {
|
||||||
|
utils.off(this.elements.container, 'keydown', trap, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle event listener
|
// Toggle event listener
|
||||||
toggleListener(elements, event, callback, toggle, passive, capture) {
|
toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
|
||||||
// Bail if no elements
|
// Bail if no elemetns, event, or callback
|
||||||
if (utils.is.nullOrUndefined(elements)) {
|
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a nodelist is passed, call itself on each node
|
// If a nodelist is passed, call itself on each node
|
||||||
if (utils.is.nodeList(elements)) {
|
if (utils.is.nodeList(elements) || utils.is.array(elements)) {
|
||||||
// Create listener for each node
|
// Create listener for each node
|
||||||
Array.from(elements).forEach(element => {
|
Array.from(elements).forEach(element => {
|
||||||
if (element instanceof Node) {
|
if (element instanceof Node) {
|
||||||
@ -533,16 +556,16 @@ const utils = {
|
|||||||
const events = event.split(' ');
|
const events = event.split(' ');
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
// Default to just capture boolean
|
// Default to just the capture boolean for browsers with no passive listener support
|
||||||
let options = utils.is.boolean(capture) ? capture : false;
|
let options = capture;
|
||||||
|
|
||||||
// If passive events listeners are supported
|
// If passive events listeners are supported
|
||||||
if (support.passiveListeners) {
|
if (support.passiveListeners) {
|
||||||
options = {
|
options = {
|
||||||
// Whether the listener can be passive (i.e. default never prevented)
|
// Whether the listener can be passive (i.e. default never prevented)
|
||||||
passive: utils.is.boolean(passive) ? passive : true,
|
passive,
|
||||||
// Whether the listener is a capturing listener or not
|
// Whether the listener is a capturing listener or not
|
||||||
capture: utils.is.boolean(capture) ? capture : false,
|
capture,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,19 +576,19 @@ const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Bind event handler
|
// Bind event handler
|
||||||
on(element, events, callback, passive, capture) {
|
on(element, events = '', callback, passive = true, capture = false) {
|
||||||
utils.toggleListener(element, events, callback, true, passive, capture);
|
utils.toggleListener(element, events, callback, true, passive, capture);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Unbind event handler
|
// Unbind event handler
|
||||||
off(element, events, callback, passive, capture) {
|
off(element, events = '', callback, passive = true, capture = false) {
|
||||||
utils.toggleListener(element, events, callback, false, passive, capture);
|
utils.toggleListener(element, events, callback, false, passive, capture);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
dispatchEvent(element, type, bubbles, detail) {
|
dispatchEvent(element, type, bubbles, detail) {
|
||||||
// Bail if no element
|
// Bail if no element
|
||||||
if (!element || !type) {
|
if (!utils.is.element(element) || !utils.is.string(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,7 +596,7 @@ const utils = {
|
|||||||
const event = new CustomEvent(type, {
|
const event = new CustomEvent(type, {
|
||||||
bubbles: utils.is.boolean(bubbles) ? bubbles : false,
|
bubbles: utils.is.boolean(bubbles) ? bubbles : false,
|
||||||
detail: Object.assign({}, detail, {
|
detail: Object.assign({}, detail, {
|
||||||
plyr: this instanceof Plyr ? this : null,
|
plyr: utils.is.plyr(this) ? this : null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -584,6 +607,12 @@ const utils = {
|
|||||||
// Toggle aria-pressed state on a toggle button
|
// Toggle aria-pressed state on a toggle button
|
||||||
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
|
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
|
||||||
toggleState(element, input) {
|
toggleState(element, input) {
|
||||||
|
// If multiple elements passed
|
||||||
|
if (utils.is.array(element) || utils.is.nodeList(element)) {
|
||||||
|
Array.from(element).forEach(target => utils.toggleState(target, input));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Bail if no target
|
// Bail if no target
|
||||||
if (!utils.is.element(element)) {
|
if (!utils.is.element(element)) {
|
||||||
return;
|
return;
|
||||||
@ -602,13 +631,20 @@ const utils = {
|
|||||||
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);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Time helpers
|
// Time helpers
|
||||||
getHours(value) { return parseInt((value / 60 / 60) % 60, 10); },
|
getHours(value) {
|
||||||
getMinutes(value) { return parseInt((value / 60) % 60, 10); },
|
return parseInt((value / 60 / 60) % 60, 10);
|
||||||
getSeconds(value) { return parseInt(value % 60, 10); },
|
},
|
||||||
|
getMinutes(value) {
|
||||||
|
return parseInt((value / 60) % 60, 10);
|
||||||
|
},
|
||||||
|
getSeconds(value) {
|
||||||
|
return parseInt(value % 60, 10);
|
||||||
|
},
|
||||||
|
|
||||||
// Format time to UI friendly string
|
// Format time to UI friendly string
|
||||||
formatTime(time = 0, displayHours = false, inverted = false) {
|
formatTime(time = 0, displayHours = false, inverted = false) {
|
||||||
@ -636,6 +672,44 @@ const utils = {
|
|||||||
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Replace all occurances of a string in a string
|
||||||
|
replaceAll(input = '', find = '', replace = '') {
|
||||||
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert to title case
|
||||||
|
toTitleCase(input = '') {
|
||||||
|
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert string to pascalCase
|
||||||
|
toPascalCase(input = '') {
|
||||||
|
let string = input.toString();
|
||||||
|
|
||||||
|
// Convert kebab case
|
||||||
|
string = utils.replaceAll(string, '-', ' ');
|
||||||
|
|
||||||
|
// Convert snake case
|
||||||
|
string = utils.replaceAll(string, '_', ' ');
|
||||||
|
|
||||||
|
// Convert to title case
|
||||||
|
string = utils.toTitleCase(string);
|
||||||
|
|
||||||
|
// Convert to pascal case
|
||||||
|
return utils.replaceAll(string, ' ', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert string to pascalCase
|
||||||
|
toCamelCase(input = '') {
|
||||||
|
let string = input.toString();
|
||||||
|
|
||||||
|
// Convert to pascal case
|
||||||
|
string = utils.toPascalCase(string);
|
||||||
|
|
||||||
|
// Convert first character to lowercase
|
||||||
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
||||||
|
},
|
||||||
|
|
||||||
// Deep extend destination object with N more objects
|
// Deep extend destination object with N more objects
|
||||||
extend(target = {}, ...sources) {
|
extend(target = {}, ...sources) {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
@ -779,7 +853,7 @@ const utils = {
|
|||||||
|
|
||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
repaint(element) {
|
repaint(element) {
|
||||||
window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
utils.toggleHidden(element, true);
|
utils.toggleHidden(element, true);
|
||||||
element.offsetHeight; // eslint-disable-line
|
element.offsetHeight; // eslint-disable-line
|
||||||
utils.toggleHidden(element, false);
|
utils.toggleHidden(element, false);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
font-family: $plyr-font-family;
|
font-family: $plyr-font-family;
|
||||||
|
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
||||||
font-weight: $plyr-font-weight-regular;
|
font-weight: $plyr-font-weight-regular;
|
||||||
line-height: $plyr-line-height;
|
line-height: $plyr-line-height;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__badge {
|
.plyr__badge {
|
||||||
background: $plyr-menu-color;
|
background: $plyr-badge-bg;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: $plyr-menu-bg;
|
color: $plyr-badge-color;
|
||||||
font-size: $plyr-font-size-badge;
|
font-size: $plyr-font-size-badge;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
.plyr__video-embed {
|
.plyr__video-embed {
|
||||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||||
$padding: ((100 / 16) * 9);
|
$padding: ((100 / 16) * 9);
|
||||||
$height: 200;
|
$height: 240;
|
||||||
$offset: to-percentage(($height - $padding) / ($height / 50));
|
$offset: to-percentage(($height - $padding) / ($height / 50));
|
||||||
|
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -59,6 +59,14 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: $plyr-control-padding;
|
padding: $plyr-control-padding;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
@ -76,7 +84,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--forward {
|
&--forward {
|
||||||
@ -100,7 +107,6 @@
|
|||||||
margin-bottom: floor($plyr-control-padding / 2);
|
margin-bottom: floor($plyr-control-padding / 2);
|
||||||
padding-left: ceil($plyr-control-padding * 4);
|
padding-left: ceil($plyr-control-padding * 4);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: calc(100% - #{$horizontal-padding});
|
width: calc(100% - #{$horizontal-padding});
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -91,6 +91,21 @@
|
|||||||
@include plyr-tab-focus();
|
@include plyr-tab-focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video range inputs
|
||||||
|
.plyr--full-ui.plyr--video input[type='range'] {
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
background-color: $plyr-video-range-track-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-track {
|
||||||
|
background-color: $plyr-video-range-track-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-track {
|
||||||
|
background-color: $plyr-video-range-track-bg;
|
||||||
|
}
|
||||||
|
|
||||||
// Pressed styles
|
// Pressed styles
|
||||||
&:active {
|
&:active {
|
||||||
@ -108,21 +123,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video range inputs
|
|
||||||
.plyr--full-ui.plyr--video input[type='range'] {
|
|
||||||
&::-webkit-slider-runnable-track {
|
|
||||||
background-color: $plyr-video-range-track-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-track {
|
|
||||||
background-color: $plyr-video-range-track-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-track {
|
|
||||||
background-color: $plyr-video-range-track-bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio range inputs
|
// Audio range inputs
|
||||||
.plyr--full-ui.plyr--audio input[type='range'] {
|
.plyr--full-ui.plyr--audio input[type='range'] {
|
||||||
&::-webkit-slider-runnable-track {
|
&::-webkit-slider-runnable-track {
|
||||||
@ -136,4 +136,19 @@
|
|||||||
&::-ms-track {
|
&::-ms-track {
|
||||||
background-color: $plyr-audio-range-track-bg;
|
background-color: $plyr-audio-range-track-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pressed styles
|
||||||
|
&:active {
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-thumb {
|
||||||
|
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
width: $plyr-range-thumb-height;
|
width: $plyr-range-thumb-height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin plyr-range-thumb-active() {
|
@mixin plyr-range-thumb-active($color: rgba($plyr-range-thumb-bg, 0.5)) {
|
||||||
box-shadow: 0 0 0 $plyr-range-thumb-active-shadow-width transparentize($plyr-range-thumb-bg, 0.5);
|
box-shadow: $plyr-range-thumb-shadow, 0 0 0 $plyr-range-thumb-active-shadow-width $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fullscreen styles
|
// Fullscreen styles
|
||||||
@ -87,6 +87,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide cursor in fullscreen when controls hidden
|
||||||
|
&.plyr--hide-controls {
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
// Large captions in full screen on larger screens
|
// Large captions in full screen on larger screens
|
||||||
@media (min-width: $plyr-bp-lg) {
|
@media (min-width: $plyr-bp-lg) {
|
||||||
.plyr__captions {
|
.plyr__captions {
|
||||||
|
@ -1,16 +1,27 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Advertisments
|
// Advertisements
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.plyr__ads {
|
.plyr__ads {
|
||||||
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 3; // Above the controls
|
z-index: -1; // Hide it by default
|
||||||
|
|
||||||
|
// Make sure the inner container is big enough for the ad creative.
|
||||||
|
> div,
|
||||||
|
> div iframe {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The countdown label
|
||||||
&::after {
|
&::after {
|
||||||
background: rgba($plyr-color-gunmetal, 0.8);
|
background: rgba($plyr-color-gunmetal, 0.8);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
@import 'settings/cosmetics';
|
@import 'settings/cosmetics';
|
||||||
@import 'settings/type';
|
@import 'settings/type';
|
||||||
|
|
||||||
|
@import 'settings/badges';
|
||||||
@import 'settings/captions';
|
@import 'settings/captions';
|
||||||
@import 'settings/controls';
|
@import 'settings/controls';
|
||||||
@import 'settings/helpers';
|
@import 'settings/helpers';
|
||||||
|
6
src/sass/settings/badges.scss
Normal file
6
src/sass/settings/badges.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Badges
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
$plyr-badge-bg: $plyr-color-fiord !default;
|
||||||
|
$plyr-badge-color: #fff !default;
|
@ -9,7 +9,7 @@ $plyr-range-thumb-active-shadow-width: 3px !default;
|
|||||||
$plyr-range-thumb-height: 14px !default;
|
$plyr-range-thumb-height: 14px !default;
|
||||||
$plyr-range-thumb-bg: #fff !default;
|
$plyr-range-thumb-bg: #fff !default;
|
||||||
$plyr-range-thumb-border: 2px solid transparent !default;
|
$plyr-range-thumb-border: 2px solid transparent !default;
|
||||||
$plyr-range-thumb-shadow: 0 1px 1px rgba($plyr-video-controls-bg, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
||||||
|
|
||||||
// Track
|
// Track
|
||||||
$plyr-range-track-height: 6px !default;
|
$plyr-range-track-height: 6px !default;
|
||||||
@ -21,3 +21,4 @@ $plyr-range-fill-bg: $plyr-color-main !default;
|
|||||||
// Type specific
|
// Type specific
|
||||||
$plyr-video-range-track-bg: $plyr-video-progress-buffered-bg !default;
|
$plyr-video-range-track-bg: $plyr-video-progress-buffered-bg !default;
|
||||||
$plyr-audio-range-track-bg: $plyr-audio-progress-buffered-bg !default;
|
$plyr-audio-range-track-bg: $plyr-audio-progress-buffered-bg !default;
|
||||||
|
$plyr-audio-range-thumb-shadow-color: rgba(#000, 0.1) !default;
|
||||||
|
Reference in New Issue
Block a user