aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Potts <sam@selz.com>2017-04-25 12:37:35 +1000
committerGitHub <noreply@github.com>2017-04-25 12:37:35 +1000
commit0a58cbfd59de14825ef894a1943651a66e8639a6 (patch)
tree238bc4769474f4c9329dd9136fed1ab0050d9473
parent2ecca2cbe3e67aa84c7b10cf1d876f2ba03eac12 (diff)
parentbbe4b7e565f6fd8acc946065934bdeae8d0dfc0e (diff)
downloadplyr-0a58cbfd59de14825ef894a1943651a66e8639a6.tar.lz
plyr-0a58cbfd59de14825ef894a1943651a66e8639a6.tar.xz
plyr-0a58cbfd59de14825ef894a1943651a66e8639a6.zip
Merge branch 'develop' into master
-rw-r--r--.gitignore8
-rw-r--r--.jsbeautifyrc23
-rw-r--r--.jshintrc4
-rw-r--r--demo/dist/demo.js2
-rw-r--r--demo/index.html8
-rw-r--r--demo/src/js/main.js133
-rw-r--r--demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt29
-rw-r--r--demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt29
-rw-r--r--dist/plyr.css2
-rw-r--r--dist/plyr.js4
-rw-r--r--dist/plyr.svg2
-rw-r--r--gulpfile.js28
-rw-r--r--notes.md34
-rw-r--r--package.json12
-rw-r--r--readme.md79
-rw-r--r--src/js/plyr.js4383
-rw-r--r--src/less/plyr.less307
-rw-r--r--src/less/variables.less13
-rw-r--r--src/scss/plyr.scss26
-rw-r--r--src/scss/variables.scss2
-rw-r--r--src/sprite/plyr-airplay.svg7
-rw-r--r--src/sprite/plyr-pip.svg7
-rw-r--r--src/sprite/plyr-settings.svg6
23 files changed, 3419 insertions, 1729 deletions
diff --git a/.gitignore b/.gitignore
index 610716ad..c037243e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,9 @@ node_modules
*.sublime-workspace
.DS_Store
aws.json
-docs/index.dev.html
*.mp4
-index-dev.html
+index-*.html
notes.txt
-*.vtt
-docs/index.dev.php
+.idea
+.DS_Store
+npm-debug.log \ No newline at end of file
diff --git a/.jsbeautifyrc b/.jsbeautifyrc
index 064bf950..10bda02e 100644
--- a/.jsbeautifyrc
+++ b/.jsbeautifyrc
@@ -6,6 +6,27 @@
"allowed_file_extensions": []
},
"js": {
- "allowed_file_extensions": []
+ "allowed_file_extensions": ["js", "json", "jsbeautifyrc"],
+ "brace_style": "collapse",
+ "break_chained_methods": false,
+ "e4x": false,
+ "end_with_newline": false,
+ "indent_char": " ",
+ "indent_level": 0,
+ "indent_size": 4,
+ "indent_with_tabs": false,
+ "jslint_happy": false,
+ "keep_array_indentation": true,
+ "keep_function_indentation": true,
+ "max_preserve_newlines": 2,
+ "preserve_newlines": true,
+ "space_after_anon_function": false,
+ "space_before_conditional": true,
+ "space_in_empty_paren": false,
+ "space_in_paren": false,
+ "unescape_strings": false,
+ "wrap_line_length": 0,
+ "wrap_attributes": "auto",
+ "wrap_attributes_indent_size": 4
}
}
diff --git a/.jshintrc b/.jshintrc
index 7a55e036..6f066d0c 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -12,8 +12,8 @@
"jquery" : false,
// Development.
- "debug" : true, // Allow debugger statements e.g. browser breakpoints.
- "devel" : true, // Allow developments statements e.g. `console.log();`.
+ "debug" : false, // Allow debugger statements e.g. browser breakpoints.
+ "devel" : false, // Allow developments statements e.g. `console.log();`.
// ECMAScript 5.
"strict" : false, // Require `use strict` pragma in every file.
diff --git a/demo/dist/demo.js b/demo/dist/demo.js
index 3ecf85aa..715cc7ad 100644
--- a/demo/dist/demo.js
+++ b/demo/dist/demo.js
@@ -1 +1 @@
-"document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var e=document.createElement("_");if(e.classList.add("c1","c2"),!e.classList.contains("c2")){var t=function(e){var t=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(e){var i,s=arguments.length;for(i=0;i<s;i++)e=arguments[i],t.call(this,e)}};t("add"),t("remove")}if(e.classList.toggle("c3",!1),e.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:i.call(this,e)}}e=null}():!function(e){"use strict";if("Element"in e){var t="classList",i="prototype",s=e.Element[i],o=Object,n=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[i].indexOf||function(e){for(var t=0,i=this.length;t<i;t++)if(t in this&&this[t]===e)return t;return-1},a=function(e,t){this.name=e,this.code=DOMException[e],this.message=t},c=function(e,t){if(""===t)throw new a("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(t))throw new a("INVALID_CHARACTER_ERR","String contains an invalid character");return r.call(e,t)},l=function(e){for(var t=n.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],s=0,o=i.length;s<o;s++)this.push(i[s]);this._updateClassName=function(){e.setAttribute("class",this.toString())}},u=l[i]=[],d=function(){return new l(this)};if(a[i]=Error[i],u.item=function(e){return this[e]||null},u.contains=function(e){return e+="",c(this,e)!==-1},u.add=function(){var e,t=arguments,i=0,s=t.length,o=!1;do e=t[i]+"",c(this,e)===-1&&(this.push(e),o=!0);while(++i<s);o&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,s=0,o=i.length,n=!1;do for(e=i[s]+"",t=c(this,e);t!==-1;)this.splice(t,1),n=!0,t=c(this,e);while(++s<o);n&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),s=i?t!==!0&&"remove":t!==!1&&"add";return s&&this[s](e),t===!0||t===!1?t:!i},u.toString=function(){return this.join(" ")},o.defineProperty){var p={get:d,enumerable:!0,configurable:!0};try{o.defineProperty(s,t,p)}catch(e){e.number===-2146823252&&(p.enumerable=!1,o.defineProperty(s,t,p))}}else o[i].__defineGetter__&&s.__defineGetter__(t,d)}}(self)),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var s=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=s+(i?" "+t:"")}}function t(t,i){if(t in n&&(i||t!==r)&&(r.length||t!==n.video)){switch(t){case n.video:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"},{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm",type:"video/webm"}],poster:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0}]});break;case n.audio:s.source({type:"audio",title:"Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;",sources:[{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case n.youtube:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case n.vimeo:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"143418951",type:"vimeo"}]})}r=t;for(var a=o.length-1;a>=0;a--)e(o[a].parentElement,"active",!1);e(document.querySelector('[data-source="'+t+'"]').parentElement,"active",!0)}}var i=plyr.setup({debug:!0,title:"Video demo",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0}});plyr.loadSprite("dist/demo.svg");for(var s=i[0],o=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState,c=o.length-1;c>=0;c--)o[c].addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),a&&history.pushState({type:e},"","#"+e)});if(window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var l=!r.length;l&&(r=n.video),r in n&&history.replaceState({type:r},"",l?"":"#"+r),r!==n.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,s,o,n,r){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,n=t.createElement(i),r=t.getElementsByTagName(i)[0],n.async=1,n.src=s,r.parentNode.insertBefore(n,r)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview")); \ No newline at end of file
+"document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var e=document.createElement("_");if(e.classList.add("c1","c2"),!e.classList.contains("c2")){var t=function(e){var t=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(e){var i,s=arguments.length;for(i=0;i<s;i++)e=arguments[i],t.call(this,e)}};t("add"),t("remove")}if(e.classList.toggle("c3",!1),e.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:i.call(this,e)}}e=null}():!function(e){"use strict";if("Element"in e){var t="classList",i="prototype",s=e.Element[i],n=Object,o=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[i].indexOf||function(e){for(var t=0,i=this.length;t<i;t++)if(t in this&&this[t]===e)return t;return-1},a=function(e,t){this.name=e,this.code=DOMException[e],this.message=t},c=function(e,t){if(""===t)throw new a("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(t))throw new a("INVALID_CHARACTER_ERR","String contains an invalid character");return r.call(e,t)},l=function(e){for(var t=o.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],s=0,n=i.length;s<n;s++)this.push(i[s]);this._updateClassName=function(){e.setAttribute("class",this.toString())}},u=l[i]=[],d=function(){return new l(this)};if(a[i]=Error[i],u.item=function(e){return this[e]||null},u.contains=function(e){return e+="",c(this,e)!==-1},u.add=function(){var e,t=arguments,i=0,s=t.length,n=!1;do e=t[i]+"",c(this,e)===-1&&(this.push(e),n=!0);while(++i<s);n&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,s=0,n=i.length,o=!1;do for(e=i[s]+"",t=c(this,e);t!==-1;)this.splice(t,1),o=!0,t=c(this,e);while(++s<n);o&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),s=i?t!==!0&&"remove":t!==!1&&"add";return s&&this[s](e),t===!0||t===!1?t:!i},u.toString=function(){return this.join(" ")},n.defineProperty){var p={get:d,enumerable:!0,configurable:!0};try{n.defineProperty(s,t,p)}catch(e){e.number===-2146823252&&(p.enumerable=!1,n.defineProperty(s,t,p))}}else n[i].__defineGetter__&&s.__defineGetter__(t,d)}}(self)),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var s=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=s+(i?" "+t:"")}}function t(t,i){if(t in o&&(i||t!==r)&&(r.length||t!==o.video)){switch(t){case o.video:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0}]});break;case o.audio:s.source({type:"audio",title:"Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;",sources:[{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case o.youtube:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case o.vimeo:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"143418951",type:"vimeo"}]})}r=t;for(var a=n.length-1;a>=0;a--)e(n[a].parentElement,"active",!1);e(document.querySelector('[data-source="'+t+'"]').parentElement,"active",!0)}}var i=plyr.setup({debug:!0,title:"Video demo",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"]});plyr.loadSprite("dist/demo.svg");for(var s=i[0],n=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState,c=n.length-1;c>=0;c--)n[c].addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),a&&history.pushState({type:e},"","#"+e)});if(window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var l=!r.length;l&&(r=o.video),r in o&&history.replaceState({type:r},"",l?"":"#"+r),r!==o.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,s,n,o,r){e.GoogleAnalyticsObject=n,e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)},e[n].l=1*new Date,o=t.createElement(i),r=t.getElementsByTagName(i)[0],o.async=1,o.src=s,r.parentNode.insertBefore(o,r)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview")); \ No newline at end of file
diff --git a/demo/index.html b/demo/index.html
index 4482ed7d..47420cdb 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -51,13 +51,13 @@
</ul>
</nav>
<section>
- <video poster="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg?v1" controls crossorigin>
+ <video poster="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg" controls crossorigin>
<!-- Video files -->
<source src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
- <source src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm">
<!-- Text track file -->
- <track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
+ <track kind="captions" label="English" srclang="en" src="webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
+ <track kind="captions" label="Français" srclang="fr" src="webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
<!-- Fallback for browsers that don't support the <video> element -->
<a href="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
@@ -73,7 +73,7 @@
</main>
<!-- Plyr core script -->
- <script src="../dist/plyr.js"></script>
+ <script src="../src/js/plyr.js"></script>
<!-- Docs script -->
<script src="dist/demo.js"></script>
diff --git a/demo/src/js/main.js b/demo/src/js/main.js
index df0418e3..7b62baf7 100644
--- a/demo/src/js/main.js
+++ b/demo/src/js/main.js
@@ -7,20 +7,33 @@
/*global plyr*/
// General functions
-;(function() {
+(function() {
//document.body.addEventListener('ready', function(event) { console.log(event); });
// Setup the player
var instances = plyr.setup({
- debug: true,
- title: 'Video demo',
- iconUrl: '../dist/plyr.svg',
+ debug: true,
+ title: 'Video demo',
+ iconUrl: '../dist/plyr.svg',
tooltips: {
- controls: true
+ controls: true
},
captions: {
- defaultActive: true
- }
+ defaultActive: true
+ },
+ controls: [
+ 'play-large',
+ 'play',
+ 'progress',
+ 'current-time',
+ 'mute',
+ 'volume',
+ 'captions',
+ 'settings',
+ 'fullscreen',
+ 'pip',
+ 'airplay'
+ ]
});
plyr.loadSprite('dist/demo.svg');
@@ -30,10 +43,10 @@
// Setup type toggle
var buttons = document.querySelectorAll('[data-source]'),
types = {
- video: 'video',
- audio: 'audio',
- youtube: 'youtube',
- vimeo: 'vimeo'
+ video: 'video',
+ audio: 'audio',
+ youtube: 'youtube',
+ vimeo: 'vimeo'
},
currentType = window.location.hash.replace('#', ''),
historySupport = (window.history && window.history.pushState);
@@ -46,34 +59,38 @@
newSource(type);
if (historySupport) {
- history.pushState({ 'type': type }, '', '#' + type);
+ history.pushState({
+ 'type': type
+ }, '', '#' + type);
}
});
}
// List for backwards/forwards
window.addEventListener('popstate', function(event) {
- if(event.state && 'type' in event.state) {
+ if (event.state && 'type' in event.state) {
newSource(event.state.type);
}
});
// On load
- if(historySupport) {
+ if (historySupport) {
var video = !currentType.length;
// If there's no current type set, assume video
- if(video) {
+ if (video) {
currentType = types.video;
}
// Replace current history state
- if(currentType in types) {
- history.replaceState({ 'type': currentType }, '', (video ? '' : '#' + currentType));
+ if (currentType in types) {
+ history.replaceState({
+ 'type': currentType
+ }, '', (video ? '' : '#' + currentType));
}
// If it's not video, load the source
- if(currentType !== types.video) {
+ if (currentType !== types.video) {
newSource(currentType, true);
}
}
@@ -83,8 +100,7 @@
if (element) {
if (element.classList) {
element.classList[state ? 'add' : 'remove'](className);
- }
- else {
+ } else {
var name = (' ' + element.className + ' ').replace(/\s+/g, ' ').replace(' ' + className + ' ', '');
element.className = name + (state ? ' ' + className : '');
}
@@ -94,29 +110,25 @@
// Set a new source
function newSource(type, init) {
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
- if(!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
+ if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
return;
}
- switch(type) {
+ switch (type) {
case types.video:
player.source({
- type: 'video',
- title: 'View From A Blue Moon',
+ type: 'video',
+ title: 'View From A Blue Moon',
sources: [{
- src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4',
- type: 'video/mp4'
- },
- {
- src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm',
- type: 'video/webm'
+ src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4',
+ type: 'video/mp4'
}],
- poster: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg',
- tracks: [{
- kind: 'captions',
- label: 'English',
- srclang:'en',
- src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
+ poster: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg',
+ tracks: [{
+ kind: 'captions',
+ label: 'English',
+ srclang: 'en',
+ src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
default: true
}]
});
@@ -124,37 +136,37 @@
case types.audio:
player.source({
- type: 'audio',
- title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
+ type: 'audio',
+ title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
sources: [{
- src: 'https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
- type: 'audio/mp3'
+ src: 'https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
+ type: 'audio/mp3'
},
- {
- src: 'https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
- type: 'audio/ogg'
+ {
+ src: 'https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
+ type: 'audio/ogg'
}]
});
break;
case types.youtube:
player.source({
- type: 'video',
- title: 'View From A Blue Moon',
+ type: 'video',
+ title: 'View From A Blue Moon',
sources: [{
- src: 'bTqVqk7FSmY',
- type: 'youtube'
+ src: 'bTqVqk7FSmY',
+ type: 'youtube'
}]
});
break;
case types.vimeo:
player.source({
- type: 'video',
- title: 'View From A Blue Moon',
+ type: 'video',
+ title: 'View From A Blue Moon',
sources: [{
- src: '143418951',
- type: 'vimeo'
+ src: '143418951',
+ type: 'vimeo'
}]
});
break;
@@ -169,17 +181,24 @@
}
// Set active on parent
- toggleClass(document.querySelector('[data-source="'+ type +'"]').parentElement, 'active', true);
+ toggleClass(document.querySelector('[data-source="' + type + '"]').parentElement, 'active', true);
}
})();
// Google analytics
// For demo site (http://[www.]plyr.io) only
-if(document.domain.indexOf('plyr.io') > -1) {
- (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');
+if (document.domain.indexOf('plyr.io') > -1) {
+ (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');
ga('create', 'UA-40881672-11', 'auto');
ga('send', 'pageview');
}
diff --git a/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt b/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt
new file mode 100644
index 00000000..5da5f307
--- /dev/null
+++ b/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt
@@ -0,0 +1,29 @@
+WEBVTT FILE
+
+1
+00:00:09.500 --> 00:00:12.000
+The ocean floor rises 5 miles to the shores
+
+2
+00:00:12.001 --> 00:00:16.500
+of what people call, the seven mile miracle
+
+3
+00:00:25.500 --> 00:00:28.000
+What would it be like to be born on this island?
+
+4
+00:00:32.500 --> 00:00:34.500
+To grow up on these shores
+
+5
+00:00:37.500 --> 00:00:40.000
+To witness this water, every day
+
+6
+00:00:43.500 --> 00:00:46.000
+You're about to meet someone, who did
+
+7
+00:02:45.500 --> 00:02:49.000
+This is a film about John John Florence \ No newline at end of file
diff --git a/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt b/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt
new file mode 100644
index 00000000..52e3af7d
--- /dev/null
+++ b/demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt
@@ -0,0 +1,29 @@
+WEBVTT FILE
+
+1
+00:00:09.500 --> 00:00:12.000
+Le fond de l'océan monte 5 miles des rives
+
+2
+00:00:12.001 --> 00:00:16.500
+de ce que les gens appellent le miracle de sept mile
+
+3
+00:00:25.500 --> 00:00:28.000
+Que serait-il d'être né sur cette île?
+
+4
+00:00:32.500 --> 00:00:34.500
+Pour grandir sur ces rivages
+
+5
+00:00:37.500 --> 00:00:40.000
+Pour assister à cette eau, tous les jours
+
+6
+00:00:43.500 --> 00:00:46.000
+Vous êtes sur le point de rencontrer quelqu'un, qui ne
+
+7
+00:02:45.500 --> 00:02:49.000
+Ceci est un film sur John John Florence \ No newline at end of file
diff --git a/dist/plyr.css b/dist/plyr.css
index e92820ce..74730c03 100644
--- a/dist/plyr.css
+++ b/dist/plyr.css
@@ -1 +1 @@
-.plyr input[type=range]:focus,.plyr:focus{outline:0}.plyr .plyr__video-embed iframe,.plyr__tooltip{pointer-events:none}@keyframes plyr-progress{to{background-position:25px 0}}.plyr{position:relative;max-width:100%;min-width:200px;font-family:Avenir,'Avenir Next','Helvetica Neue','Segoe UI',Helvetica,Arial,sans-serif;direction:ltr}.plyr,.plyr *,.plyr ::after,.plyr ::before{box-sizing:border-box}.plyr a,.plyr button,.plyr input,.plyr label{-ms-touch-action:manipulation;touch-action:manipulation}.plyr audio,.plyr video{width:100%;height:auto;vertical-align:middle;border-radius:inherit}.plyr input[type=range]{display:block;height:20px;width:100%;margin:0;padding:0;vertical-align:middle;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none;background:0 0}.plyr input[type=range]::-webkit-slider-runnable-track{height:8px;background:0 0;border:0;border-radius:4px;-webkit-user-select:none;user-select:none}.plyr input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;margin-top:-4px;position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(0,0,0,.15);box-sizing:border-box}.plyr input[type=range]::-moz-range-track{height:8px;background:0 0;border:0;border-radius:4px;-moz-user-select:none;user-select:none}.plyr input[type=range]::-moz-range-thumb{position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(0,0,0,.15);box-sizing:border-box}.plyr input[type=range]::-ms-track{height:8px;background:0 0;border:0;color:transparent}.plyr input[type=range]::-ms-fill-upper{height:8px;background:0 0;border:0;border-radius:4px;-ms-user-select:none;user-select:none}.plyr input[type=range]::-ms-fill-lower{height:8px;border:0;border-radius:4px;-ms-user-select:none;user-select:none;background:#3498db}.plyr input[type=range]::-ms-thumb{position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(0,0,0,.15);box-sizing:border-box;margin-top:0}.plyr input[type=range]::-ms-tooltip{display:none}.plyr input[type=range]::-moz-focus-outer{border:0}.plyr input[type=range].tab-focus:focus{outline-offset:3px}.plyr input[type=range]:active::-webkit-slider-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr input[type=range]:active::-moz-range-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr input[type=range]:active::-ms-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr--video input[type=range].tab-focus:focus{outline:rgba(255,255,255,.5) dotted 1px}.plyr--audio input[type=range].tab-focus:focus{outline:rgba(86,93,100,.5) dotted 1px}.plyr__sr-only{clip:rect(1px,1px,1px,1px);overflow:hidden;position:absolute!important;padding:0!important;border:0!important;height:1px!important;width:1px!important}.plyr__video-wrapper{position:relative;background:#000;border-radius:inherit}.plyr__video-embed{padding-bottom:56.25%;height:0;border-radius:inherit;overflow:hidden;z-index:0}.plyr__video-embed iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.plyr__video-embed>div{position:relative;padding-bottom:200%;transform:translateY(-35.95%)}.plyr video::-webkit-media-text-track-container{display:none}.plyr__captions{display:none;position:absolute;bottom:0;left:0;width:100%;padding:20px;transform:translateY(-40px);transition:transform .3s ease;color:#fff;font-size:16px;text-align:center;font-weight:400}.plyr__captions span{border-radius:2px;padding:3px 10px;background:rgba(0,0,0,.7);-webkit-box-decoration-break:clone;box-decoration-break:clone;line-height:150%}.plyr__captions span:empty{display:none}@media (min-width:768px){.plyr__captions{font-size:24px}}.plyr--captions-active .plyr__captions{display:block}.plyr--hide-controls .plyr__captions{transform:translateY(-15px)}@media (min-width:1024px){.plyr--fullscreen-active .plyr__captions{font-size:32px}}.plyr ::-webkit-media-controls{display:none}.plyr__controls{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;line-height:1;text-align:center}.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls>button{margin-left:5px}.plyr__controls .plyr__progress:first-child,.plyr__controls .plyr__time:first-child,.plyr__controls>button:first-child{margin-left:0}.plyr__controls .plyr__volume{margin-left:5px}.plyr__controls [data-plyr=pause]{margin-left:0}.plyr__controls button{position:relative;display:inline-block;-ms-flex-negative:0;flex-shrink:0;overflow:visible;vertical-align:middle;padding:7px;border:0;background:0 0;border-radius:3px;cursor:pointer;transition:background .3s ease,color .3s ease,opacity .3s ease;color:inherit}.plyr__controls button svg{width:18px;height:18px;display:block;fill:currentColor}.plyr__controls button:focus{outline:0}.plyr__controls .icon--captions-on,.plyr__controls .icon--exit-fullscreen,.plyr__controls .icon--muted{display:none}@media (min-width:480px){.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls>button{margin-left:10px}}.plyr--hide-controls .plyr__controls{opacity:0;pointer-events:none}.plyr--video .plyr__controls{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:50px 10px 10px;background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.5));border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;color:#fff;transition:opacity .3s ease}.plyr--video .plyr__controls button.tab-focus:focus,.plyr--video .plyr__controls button:hover{background:#3498db;color:#fff}.plyr--audio .plyr__controls{padding:10px;border-radius:inherit;background:#fff;border:1px solid #dbe3e8;color:#565D64}.plyr--audio .plyr__controls button.tab-focus:focus,.plyr--audio .plyr__controls button:hover,.plyr__play-large{background:#3498db;color:#fff}.plyr__play-large{display:none;position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);padding:10px;border:4px solid currentColor;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15);transition:all .3s ease}.plyr__play-large svg{position:relative;left:2px;width:20px;height:20px;display:block;fill:currentColor}.plyr__play-large:focus{outline:rgba(255,255,255,.5) dotted 1px}.plyr .plyr__play-large{display:inline-block}.plyr--audio .plyr__play-large,.plyr--playing .plyr__controls [data-plyr=play],.plyr__controls [data-plyr=pause]{display:none}.plyr--playing .plyr__play-large{opacity:0;visibility:hidden}.plyr--playing .plyr__controls [data-plyr=pause]{display:inline-block}.plyr--captions-active .plyr__controls .icon--captions-on,.plyr--fullscreen-active .icon--exit-fullscreen,.plyr--muted .plyr__controls .icon--muted{display:block}.plyr [data-plyr=captions],.plyr [data-plyr=fullscreen],.plyr--captions-active .plyr__controls .icon--captions-on+svg,.plyr--fullscreen-active .icon--exit-fullscreen+svg,.plyr--muted .plyr__controls .icon--muted+svg{display:none}.plyr--captions-enabled [data-plyr=captions],.plyr--fullscreen-enabled [data-plyr=fullscreen]{display:inline-block}.plyr__tooltip{position:absolute;z-index:2;bottom:100%;margin-bottom:10px;padding:5px 7.5px;opacity:0;background:rgba(0,0,0,.7);border-radius:3px;color:#fff;font-size:14px;line-height:1.3;transform:translate(-50%,10px) scale(.8);transform-origin:50% 100%;transition:transform .2s .1s ease,opacity .2s .1s ease}.plyr__tooltip::before{content:'';position:absolute;width:0;height:0;left:50%;transform:translateX(-50%);bottom:-4px;border-right:4px solid transparent;border-top:4px solid rgba(0,0,0,.7);border-left:4px solid transparent;z-index:2}.plyr button.tab-focus:focus .plyr__tooltip,.plyr button:hover .plyr__tooltip,.plyr__tooltip--visible{opacity:1;transform:translate(-50%,0) scale(1)}.plyr button:hover .plyr__tooltip{z-index:3}.plyr__controls button:first-child .plyr__tooltip{left:0;transform:translate(0,10px) scale(.8);transform-origin:0 100%}.plyr__controls button:first-child .plyr__tooltip::before{left:16px}.plyr__controls button:last-child .plyr__tooltip{right:0;transform:translate(0,10px) scale(.8);transform-origin:100% 100%}.plyr__controls button:last-child .plyr__tooltip::before{left:auto;right:16px;transform:translateX(50%)}.plyr__controls button:first-child .plyr__tooltip--visible,.plyr__controls button:first-child.tab-focus:focus .plyr__tooltip,.plyr__controls button:first-child:hover .plyr__tooltip,.plyr__controls button:last-child .plyr__tooltip--visible,.plyr__controls button:last-child.tab-focus:focus .plyr__tooltip,.plyr__controls button:last-child:hover .plyr__tooltip{transform:translate(0,0) scale(1)}.plyr__progress{position:relative;display:none;-ms-flex:1;flex:1}.plyr__progress input[type=range]{position:relative;z-index:2}.plyr__progress input[type=range]::-webkit-slider-runnable-track{background:0 0}.plyr__progress input[type=range]::-moz-range-track{background:0 0}.plyr__progress input[type=range]::-ms-fill-upper{background:0 0}.plyr__progress .plyr__tooltip{left:0}.plyr .plyr__progress{display:inline-block}.plyr__progress--buffer,.plyr__progress--played,.plyr__volume--display{position:absolute;left:0;top:50%;width:100%;height:8px;margin:-4px 0 0;padding:0;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;border-radius:100px}.plyr__progress--buffer::-webkit-progress-bar,.plyr__progress--played::-webkit-progress-bar,.plyr__volume--display::-webkit-progress-bar{background:0 0}.plyr__progress--buffer::-webkit-progress-value,.plyr__progress--played::-webkit-progress-value,.plyr__volume--display::-webkit-progress-value{background:currentColor;border-radius:100px;min-width:8px}.plyr__progress--buffer::-moz-progress-bar,.plyr__progress--played::-moz-progress-bar,.plyr__volume--display::-moz-progress-bar{background:currentColor;border-radius:100px;min-width:8px}.plyr__progress--buffer::-ms-fill,.plyr__progress--played::-ms-fill,.plyr__volume--display::-ms-fill{border-radius:100px}.plyr__progress--played,.plyr__volume--display{z-index:1;color:#3498db;background:0 0;transition:none}.plyr__progress--played::-webkit-progress-value,.plyr__volume--display::-webkit-progress-value{min-width:8px;max-width:99%;border-top-right-radius:0;border-bottom-right-radius:0;transition:none}.plyr__progress--played::-moz-progress-bar,.plyr__volume--display::-moz-progress-bar{min-width:8px;max-width:99%;border-top-right-radius:0;border-bottom-right-radius:0;transition:none}.plyr__progress--played::-ms-fill,.plyr__volume--display::-ms-fill{display:none}.plyr__progress--buffer::-webkit-progress-value{transition:width .2s ease}.plyr__progress--buffer::-moz-progress-bar{transition:width .2s ease}.plyr__progress--buffer::-ms-fill{transition:width .2s ease}.plyr--video .plyr__progress--buffer,.plyr--video .plyr__volume--display{background:rgba(255,255,255,.25)}.plyr--video .plyr__progress--buffer{color:rgba(255,255,255,.25)}.plyr--audio .plyr__progress--buffer,.plyr--audio .plyr__volume--display{background:rgba(198,214,219,.66)}.plyr--audio .plyr__progress--buffer{color:rgba(198,214,219,.66)}.plyr--loading .plyr__progress--buffer{animation:plyr-progress 1s linear infinite;background-size:25px 25px;background-repeat:repeat-x;background-image:linear-gradient(-45deg,rgba(0,0,0,.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,.15) 50%,rgba(0,0,0,.15) 75%,transparent 75%,transparent);color:transparent}.plyr--video.plyr--loading .plyr__progress--buffer{background-color:rgba(255,255,255,.25)}.plyr--audio.plyr--loading .plyr__progress--buffer{background-color:rgba(198,214,219,.66)}.plyr__time{display:inline-block;vertical-align:middle;font-size:14px}.plyr__time+.plyr__time{display:none}@media (min-width:768px){.plyr__time+.plyr__time{display:inline-block}}.plyr__time+.plyr__time::before{content:'\2044';margin-right:10px}.plyr__volume{display:none}.plyr .plyr__volume{-ms-flex:1;flex:1;position:relative}.plyr .plyr__volume input[type=range]{position:relative;z-index:2}@media (min-width:480px){.plyr .plyr__volume{display:block;max-width:60px}}@media (min-width:768px){.plyr .plyr__volume{max-width:100px}}.plyr--is-ios .plyr__volume,.plyr--is-ios [data-plyr=mute]{display:none!important}.plyr--fullscreen-active{position:fixed;top:0;left:0;right:0;bottom:0;height:100%;width:100%;z-index:10000000;background:#000;border-radius:0!important}.plyr--fullscreen-active video{height:100%}.plyr--fullscreen-active .plyr__video-wrapper{height:100%;width:100%}.plyr--fullscreen-active .plyr__video-embed{overflow:visible}.plyr--fullscreen-active .plyr__controls{position:absolute;bottom:0;left:0;right:0}.plyr--fullscreen-active.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)} \ No newline at end of file
+.plyr input[type=range]:focus,.plyr:focus{outline:0}@keyframes plyr-progress{to{background-position:25px 0}}@keyframes plyr-popup{from{transform:translateY(10px);opacity:.5}to{transform:translateY(0);opacity:1}}.plyr{position:relative;max-width:100%;min-width:200px;font-family:Avenir,'Avenir Next','Helvetica Neue','Segoe UI',Helvetica,Arial,sans-serif;font-weight:500;direction:ltr}.plyr,.plyr *,.plyr ::after,.plyr ::before{box-sizing:border-box}.plyr a,.plyr button,.plyr input,.plyr label{-ms-touch-action:manipulation;touch-action:manipulation}.plyr [aria-hidden=true]{display:none}.plyr audio,.plyr video{width:100%;height:auto;vertical-align:middle;border-radius:inherit}.plyr input[type=range]{display:block;height:20px;width:100%;margin:0;padding:0;vertical-align:middle;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none;background:0 0}.plyr input[type=range]::-webkit-slider-runnable-track{height:8px;background:0 0;border:0;border-radius:4px;-webkit-user-select:none;user-select:none}.plyr input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;margin-top:-4px;position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(52,63,74,.2);box-sizing:border-box}.plyr input[type=range]::-moz-range-track{height:8px;background:0 0;border:0;border-radius:4px;-moz-user-select:none;user-select:none}.plyr input[type=range]::-moz-range-thumb{position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(52,63,74,.2);box-sizing:border-box}.plyr input[type=range]::-ms-track{height:8px;background:0 0;border:0;color:transparent}.plyr input[type=range]::-ms-fill-upper{height:8px;background:0 0;border:0;border-radius:4px;-ms-user-select:none;user-select:none}.plyr input[type=range]::-ms-fill-lower{height:8px;border:0;border-radius:4px;-ms-user-select:none;user-select:none;background:#3498db}.plyr input[type=range]::-ms-thumb{position:relative;height:16px;width:16px;background:#fff;border:2px solid transparent;border-radius:100%;transition:background .2s ease,border .2s ease,transform .2s ease;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(52,63,74,.2);box-sizing:border-box;margin-top:0}.plyr input[type=range]::-ms-tooltip{display:none}.plyr input[type=range]::-moz-focus-outer{border:0}.plyr input[type=range].tab-focus:focus{outline-offset:3px}.plyr input[type=range]:active::-webkit-slider-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr input[type=range]:active::-moz-range-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr input[type=range]:active::-ms-thumb{background:#3498db;border-color:#fff;transform:scale(1.25)}.plyr--video input[type=range].tab-focus:focus{outline:rgba(255,255,255,.5) dotted 1px}.plyr--audio input[type=range].tab-focus:focus{outline:rgba(86,93,100,.5) dotted 1px}.plyr__sr-only{clip:rect(1px,1px,1px,1px);overflow:hidden;position:absolute!important;padding:0!important;border:0!important;height:1px!important;width:1px!important}.plyr__video-embed,.plyr__video-wrapper{border-radius:inherit;overflow:hidden;z-index:0}.plyr__video-wrapper{position:relative;background:#000}.plyr__video-embed{padding-bottom:56.25%;height:0}.plyr__video-embed iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.plyr__video-embed>div{position:relative;padding-bottom:200%;transform:translateY(-35.95%)}.plyr .plyr__video-embed iframe{pointer-events:none}.plyr video::-webkit-media-text-track-container{display:none}.plyr__captions{display:none;position:absolute;bottom:0;left:0;width:100%;padding:20px;transform:translateY(-40px);transition:transform .3s ease;color:#fff;font-size:16px;text-align:center}.plyr__captions span{border-radius:2px;padding:3px 10px;background:rgba(0,0,0,.6);-webkit-box-decoration-break:clone;box-decoration-break:clone;line-height:150%}.plyr__captions span:empty{display:none}@media (min-width:768px){.plyr__captions{font-size:24px}}.plyr--captions-active .plyr__captions{display:block}.plyr--hide-controls .plyr__captions{transform:translateY(-15px)}@media (min-width:1024px){.plyr--fullscreen-active .plyr__captions{font-size:32px}}.plyr ::-webkit-media-controls{display:none}.plyr__controls{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;line-height:1;text-align:center}.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls>.plyr__control{margin-left:5px}.plyr__controls .plyr__menu:first-child,.plyr__controls .plyr__menu:first-child+[data-plyr=pause],.plyr__controls .plyr__progress:first-child,.plyr__controls .plyr__progress:first-child+[data-plyr=pause],.plyr__controls .plyr__time:first-child,.plyr__controls .plyr__time:first-child+[data-plyr=pause],.plyr__controls>.plyr__control:first-child,.plyr__controls>.plyr__control:first-child+[data-plyr=pause]{margin-left:0}.plyr__controls .plyr__volume{margin-left:5px}@media (min-width:480px){.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls>.plyr__control{margin-left:10px}.plyr__controls .plyr__menu+.plyr__control,.plyr__controls>.plyr__control+.plyr__control,.plyr__controls>.plyr__control+.plyr__menu{margin-left:5px}}.plyr--hide-controls .plyr__controls{opacity:0;pointer-events:none}.plyr__control{position:relative;display:inline-block;-ms-flex-negative:0;flex-shrink:0;overflow:visible;vertical-align:middle;padding:7px;border:0;background:0 0;border-radius:3px;cursor:pointer;transition:background .3s ease,color .3s ease,opacity .3s ease;color:inherit}.plyr__control svg{width:18px;height:18px;display:block;fill:currentColor;pointer-events:none}.plyr__control .icon--captions-on,.plyr__control .icon--exit-fullscreen,.plyr__control .icon--muted{display:none}.plyr__control:focus{outline:0}.plyr--video .plyr__controls{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:50px 10px 10px;background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.7));border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;color:#fff;transition:opacity .3s ease}.plyr--video .plyr__controls .plyr__control.tab-focus:focus,.plyr--video .plyr__controls .plyr__control:hover,.plyr--video .plyr__controls .plyr__control[aria-expanded=true]{background:#3498db;color:#fff}.plyr--audio .plyr__controls{padding:10px;border-radius:inherit;background:#fff;border:1px solid #dbe3e8;color:#565D64}.plyr--audio .plyr__controls .plyr__control.tab-focus:focus,.plyr--audio .plyr__controls .plyr__control:hover,.plyr--audio .plyr__controls .plyr__control[aria-expanded=true]{background:#3498db;color:#fff}.plyr__play-large{display:none;position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);padding:10px;background:#3498db;border:4px solid currentColor;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15);color:#fff;transition:all .3s ease}.plyr__play-large svg{position:relative;left:2px;width:20px;height:20px;display:block;fill:currentColor;pointer-events:none}.plyr__play-large:focus{outline:rgba(255,255,255,.5) dotted 1px}.plyr .plyr__play-large{display:inline-block}.plyr--audio .plyr__play-large,.plyr--playing .plyr__controls [data-plyr=play],.plyr__controls [data-plyr=pause]{display:none}.plyr--playing .plyr__play-large{opacity:0;visibility:hidden}.plyr--playing .plyr__controls [data-plyr=pause]{display:inline-block}.plyr--captions-active .plyr__control .icon--captions-on,.plyr--fullscreen-active .plyr__control .icon--exit-fullscreen,.plyr--muted .plyr__control .icon--muted{display:block}.plyr [data-plyr=pip],.plyr [data-plyr=airplay],.plyr [data-plyr=captions],.plyr [data-plyr=fullscreen],.plyr--captions-active .plyr__control .icon--captions-on+svg,.plyr--fullscreen-active .plyr__control .icon--exit-fullscreen+svg,.plyr--muted .plyr__control .icon--muted+svg{display:none}.plyr--airplay-enabled [data-plyr=airplay],.plyr--captions-enabled [data-plyr=captions],.plyr--fullscreen-enabled [data-plyr=fullscreen],.plyr--pip-enabled [data-plyr=pip]{display:inline-block}.plyr__menu{position:relative}.plyr__menu .plyr__control svg{transition:transform .3s ease}.plyr__menu__container.is-resizing,.plyr__menu__container>div{overflow:hidden;transition:height .35s cubic-bezier(.4,0,.2,1),width .35s cubic-bezier(.4,0,.2,1)}.plyr__menu .plyr__control[aria-expanded=true] svg{transform:rotate(45deg)}.plyr__menu .plyr__control[aria-expanded=true] .plyr__tooltip{display:none}.plyr__menu__container{position:absolute;z-index:1;bottom:100%;right:-5px;margin-bottom:10px;animation:plyr-popup .2s ease;background:rgba(52,63,74,.9);border-radius:4px;white-space:nowrap;text-align:left;color:#fff;font-size:14px}.plyr__menu__container::after{content:"";position:absolute;top:100%;right:15px;height:0;width:0;border:6px solid transparent;border-top-color:rgba(52,63,74,.9)}.plyr__menu__container ul{margin:0;padding:7px;list-style:none;overflow:hidden}.plyr__menu__container .plyr__control{display:-ms-flexbox;display:flex;width:100%;padding:7px 14px;color:#fff;font-weight:600;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.plyr__menu__container .plyr__control::after{content:"";position:absolute;top:50%;transform:translateY(-50%);border:5px solid transparent}.plyr__menu__container .plyr__control--forward{padding-right:28px}.plyr__menu__container .plyr__control--forward::after{right:5px;border-left-color:rgba(255,255,255,.8)}.plyr__menu__container .plyr__control--back{position:relative;width:calc(100% - 14px);margin:7px 7px 3px;padding-left:28px;font-weight:500}.plyr__menu__container .plyr__control--back::after{left:5px;border-right-color:rgba(255,255,255,.8)}.plyr__menu__container .plyr__control--back::before{content:"";position:absolute;top:100%;left:0;right:0;height:1px;overflow:hidden;margin-top:4px;background:rgba(0,0,0,.15);box-shadow:0 1px 0 rgba(255,255,255,.1)}.plyr__menu__container label.plyr__control{padding-left:18px}.plyr__menu__container label.plyr__control input[type=radio]{position:relative;left:-7px}.plyr__menu__container .plyr__menu__value{display:inherit;margin-left:auto;padding-left:25px;pointer-events:none;overflow:hidden;font-weight:500;color:rgba(255,255,255,.8)}.plyr__menu__container .plyr__menu__value .plyr__badge{font-weight:600}.plyr__badge{padding:2px 4px;border-radius:2px;background:#fff;color:rgba(52,63,74,.9);font-size:10px}.plyr__tooltip{position:absolute;z-index:2;bottom:100%;margin-bottom:10px;padding:5px 7.5px;pointer-events:none;opacity:0;background:rgba(52,63,74,.9);border-radius:3px;color:#fff;font-size:14px;font-weight:500;line-height:1.3;transform:translate(-50%,10px) scale(.8);transform-origin:50% 100%;transition:transform .2s .1s ease,opacity .2s .1s ease}.plyr__tooltip::before{content:'';position:absolute;width:0;height:0;left:50%;transform:translateX(-50%);bottom:-4px;border-right:4px solid transparent;border-top:4px solid rgba(52,63,74,.9);border-left:4px solid transparent;z-index:2}.plyr .plyr__control.tab-focus:focus .plyr__tooltip,.plyr .plyr__control:hover .plyr__tooltip,.plyr__tooltip--visible{opacity:1;transform:translate(-50%,0) scale(1)}.plyr .plyr__control:hover .plyr__tooltip{z-index:3}.plyr__controls>.plyr__control:first-child .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip{left:0;transform:translate(0,10px) scale(.8);transform-origin:0 100%}.plyr__controls>.plyr__control:first-child .plyr__tooltip::before,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip::before{left:16px}.plyr__controls>.plyr__control:last-child .plyr__tooltip{right:0;transform:translate(0,10px) scale(.8);transform-origin:100% 100%}.plyr__controls>.plyr__control:last-child .plyr__tooltip::before{left:auto;right:16px;transform:translateX(50%)}.plyr__controls>.plyr__control:first-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control.tab-focus:focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control:hover .plyr__tooltip,.plyr__controls>.plyr__control:first-child.tab-focus:focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child:hover .plyr__tooltip,.plyr__controls>.plyr__control:last-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:last-child.tab-focus:focus .plyr__tooltip,.plyr__controls>.plyr__control:last-child:hover .plyr__tooltip{transform:translate(0,0) scale(1)}.plyr__progress{position:relative;display:none;-ms-flex:1;flex:1}.plyr__progress input[type=range]{position:relative;z-index:2}.plyr__progress input[type=range]::-webkit-slider-runnable-track{background:0 0}.plyr__progress input[type=range]::-moz-range-track{background:0 0}.plyr__progress input[type=range]::-ms-fill-upper{background:0 0}.plyr__progress .plyr__tooltip{left:0}.plyr .plyr__progress{display:inline-block}.plyr__progress--buffer,.plyr__progress--played,.plyr__volume--display{position:absolute;left:0;top:50%;width:100%;height:8px;margin:-4px 0 0;padding:0;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;border-radius:100px}.plyr__progress--buffer::-webkit-progress-bar,.plyr__progress--played::-webkit-progress-bar,.plyr__volume--display::-webkit-progress-bar{background:0 0}.plyr__progress--buffer::-webkit-progress-value,.plyr__progress--played::-webkit-progress-value,.plyr__volume--display::-webkit-progress-value{background:currentColor;border-radius:100px;min-width:8px}.plyr__progress--buffer::-moz-progress-bar,.plyr__progress--played::-moz-progress-bar,.plyr__volume--display::-moz-progress-bar{background:currentColor;border-radius:100px;min-width:8px}.plyr__progress--buffer::-ms-fill,.plyr__progress--played::-ms-fill,.plyr__volume--display::-ms-fill{border-radius:100px}.plyr__progress--played,.plyr__volume--display{z-index:1;color:#3498db;background:0 0;transition:none}.plyr__progress--played::-webkit-progress-value,.plyr__volume--display::-webkit-progress-value{min-width:8px;max-width:99%;border-top-right-radius:0;border-bottom-right-radius:0;transition:none}.plyr__progress--played::-moz-progress-bar,.plyr__volume--display::-moz-progress-bar{min-width:8px;max-width:99%;border-top-right-radius:0;border-bottom-right-radius:0;transition:none}.plyr__progress--played::-ms-fill,.plyr__volume--display::-ms-fill{display:none}.plyr__progress--buffer::-webkit-progress-value{transition:width .2s ease}.plyr__progress--buffer::-moz-progress-bar{transition:width .2s ease}.plyr__progress--buffer::-ms-fill{transition:width .2s ease}.plyr--video .plyr__progress--buffer,.plyr--video .plyr__volume--display{background:rgba(255,255,255,.25)}.plyr--video .plyr__progress--buffer{color:rgba(255,255,255,.25)}.plyr--audio .plyr__progress--buffer,.plyr--audio .plyr__volume--display{background:rgba(198,214,219,.66)}.plyr--audio .plyr__progress--buffer{color:rgba(198,214,219,.66)}.plyr--loading .plyr__progress--buffer{animation:plyr-progress 1s linear infinite;background-size:25px 25px;background-repeat:repeat-x;background-image:linear-gradient(-45deg,rgba(52,63,74,.2) 25%,transparent 25%,transparent 50%,rgba(52,63,74,.2) 50%,rgba(52,63,74,.2) 75%,transparent 75%,transparent);color:transparent}.plyr--video.plyr--loading .plyr__progress--buffer{background-color:rgba(255,255,255,.25)}.plyr--audio.plyr--loading .plyr__progress--buffer{background-color:rgba(198,214,219,.66)}.plyr__time{display:inline-block;vertical-align:middle;font-size:14px}.plyr__time+.plyr__time{display:none}@media (min-width:768px){.plyr__time+.plyr__time{display:inline-block}}.plyr__time+.plyr__time::before{content:'\2044';margin-right:10px}.plyr__volume{display:none}.plyr .plyr__volume{-ms-flex:1;flex:1;position:relative}.plyr .plyr__volume input[type=range]{position:relative;z-index:2}@media (min-width:480px){.plyr .plyr__volume{display:block;max-width:60px}}@media (min-width:768px){.plyr .plyr__volume{max-width:100px}}.plyr--is-ios .plyr__volume,.plyr--is-ios [data-plyr=mute]{display:none!important}.plyr--fullscreen-active{position:fixed;top:0;left:0;right:0;bottom:0;height:100%;width:100%;z-index:10000000;background:#000;border-radius:0!important}.plyr--fullscreen-active video{height:100%}.plyr--fullscreen-active .plyr__video-wrapper{height:100%;width:100%}.plyr--fullscreen-active .plyr__video-embed{overflow:visible}.plyr--fullscreen-active .plyr__controls{position:absolute;bottom:0;left:0;right:0}.plyr--fullscreen-active.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)} \ No newline at end of file
diff --git a/dist/plyr.js b/dist/plyr.js
index c94c75c7..3fb7951f 100644
--- a/dist/plyr.js
+++ b/dist/plyr.js
@@ -1,2 +1,2 @@
-!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t(e,document):"function"==typeof define&&define.amd?define([],function(){return t(e,document)}):e.plyr=t(e,document)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(){var e,n,r,a=navigator.userAgent,s=navigator.appName,o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10),l=!1,u=!1,c=!1,d=!1;return navigator.appVersion.indexOf("Windows NT")!==-1&&navigator.appVersion.indexOf("rv:11")!==-1?(l=!0,s="IE",o="11"):(n=a.indexOf("MSIE"))!==-1?(l=!0,s="IE",o=a.substring(n+5)):(n=a.indexOf("Chrome"))!==-1?(c=!0,s="Chrome",o=a.substring(n+7)):(n=a.indexOf("Safari"))!==-1?(d=!0,s="Safari",o=a.substring(n+7),(n=a.indexOf("Version"))!==-1&&(o=a.substring(n+8))):(n=a.indexOf("Firefox"))!==-1?(u=!0,s="Firefox",o=a.substring(n+8)):(e=a.lastIndexOf(" ")+1)<(n=a.lastIndexOf("/"))&&(s=a.substring(e,n),o=a.substring(n+1),s.toLowerCase()===s.toUpperCase()&&(s=navigator.appName)),(r=o.indexOf(";"))!==-1&&(o=o.substring(0,r)),(r=o.indexOf(" "))!==-1&&(o=o.substring(0,r)),i=parseInt(""+o,10),isNaN(i)&&(o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10)),{name:s,version:i,isIE:l,isFirefox:u,isChrome:c,isSafari:d,isIos:/(iPad|iPhone|iPod)/g.test(navigator.platform),isIphone:/(iPhone|iPod)/g.test(navigator.userAgent),isTouch:"ontouchstart"in t.documentElement}}function r(e,t){var n=e.media;if("video"===e.type)switch(t){case"video/webm":return!(!n.canPlayType||!n.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/,""));case"video/mp4":return!(!n.canPlayType||!n.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/,""));case"video/ogg":return!(!n.canPlayType||!n.canPlayType('video/ogg; codecs="theora"').replace(/no/,""))}else if("audio"===e.type)switch(t){case"audio/mpeg":return!(!n.canPlayType||!n.canPlayType("audio/mpeg;").replace(/no/,""));case"audio/ogg":return!(!n.canPlayType||!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/,""));case"audio/wav":return!(!n.canPlayType||!n.canPlayType('audio/wav; codecs="1"').replace(/no/,""))}return!1}function a(e){if(!t.querySelectorAll('script[src="'+e+'"]').length){var n=t.createElement("script");n.src=e;var r=t.getElementsByTagName("script")[0];r.parentNode.insertBefore(n,r)}}function s(e,t){return Array.prototype.indexOf&&e.indexOf(t)!==-1}function o(e,t,n){return e.replace(new RegExp(t.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g,"\\$1"),"g"),n)}function i(e,t){e.length||(e=[e]);for(var n=e.length-1;n>=0;n--){var r=n>0?t.cloneNode(!0):t,a=e[n],s=a.parentNode,o=a.nextSibling;return r.appendChild(a),o?s.insertBefore(r,o):s.appendChild(r),r}}function l(e){e&&e.parentNode.removeChild(e)}function u(e,t){e.insertBefore(t,e.firstChild)}function c(e,t){for(var n in t)e.setAttribute(n,O.boolean(t[n])&&t[n]?"":t[n])}function d(e,n,r){var a=t.createElement(e);c(a,r),u(n,a)}function p(e){return e.replace(".","")}function m(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var r=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=r+(n?" "+t:"")}}function f(e,t){return!!e&&(e.classList?e.classList.contains(t):new RegExp("(\\s|^)"+t+"(\\s|$)").test(e.className))}function y(e,n){var r=Element.prototype,a=r.matches||r.webkitMatchesSelector||r.mozMatchesSelector||r.msMatchesSelector||function(e){return[].indexOf.call(t.querySelectorAll(e),this)!==-1};return a.call(e,n)}function b(e,t,n,r,a){g(e,t,function(t){n&&n.apply(e,[t]),r.apply(e,[t])},a)}function v(e,t,n,r,a){var s=t.split(" ");if(O.boolean(a)||(a=!1),e instanceof NodeList)for(var o=0;o<e.length;o++)e[o]instanceof Node&&v(e[o],arguments[1],arguments[2],arguments[3]);else for(var i=0;i<s.length;i++)e[r?"addEventListener":"removeEventListener"](s[i],n,a)}function g(e,t,n,r){e&&v(e,t,n,!0,r)}function h(e,t,n,r){if(e&&t){O.boolean(n)||(n=!1);var a=new CustomEvent(t,{bubbles:n,detail:r});e.dispatchEvent(a)}}function k(e,t){if(e)return t=O.boolean(t)?t:!e.getAttribute("aria-pressed"),e.setAttribute("aria-pressed",t),t}function w(e,t){return 0===e||0===t||isNaN(e)||isNaN(t)?0:(e/t*100).toFixed(2)}function x(){var e=arguments;if(e.length){if(1===e.length)return e[0];for(var t=Array.prototype.shift.call(e),n=e.length,r=0;r<n;r++){var a=e[r];for(var s in a)a[s]&&a[s].constructor&&a[s].constructor===Object?(t[s]=t[s]||{},x(t[s],a[s])):t[s]=a[s]}return t}}function T(e){var t=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;return e.match(t)?RegExp.$2:e}function S(e){var t=/^.*(vimeo.com\/|video\/)(\d+).*/;return e.match(t)?RegExp.$2:e}function _(){var e={supportsFullScreen:!1,isFullScreen:function(){return!1},requestFullScreen:function(){},cancelFullScreen:function(){},fullScreenEventName:"",element:null,prefix:""},n="webkit o moz ms khtml".split(" ");if(O.undefined(t.cancelFullScreen))for(var r=0,a=n.length;r<a;r++){if(e.prefix=n[r],!O.undefined(t[e.prefix+"CancelFullScreen"])){e.supportsFullScreen=!0;break}if(!O.undefined(t.msExitFullscreen)&&t.msFullscreenEnabled){e.prefix="ms",e.supportsFullScreen=!0;break}}else e.supportsFullScreen=!0;return e.supportsFullScreen&&(e.fullScreenEventName="ms"===e.prefix?"MSFullscreenChange":e.prefix+"fullscreenchange",e.isFullScreen=function(e){switch(O.undefined(e)&&(e=t.body),this.prefix){case"":return t.fullscreenElement===e;case"moz":return t.mozFullScreenElement===e;default:return t[this.prefix+"FullscreenElement"]===e}},e.requestFullScreen=function(e){return O.undefined(e)&&(e=t.body),""===this.prefix?e.requestFullScreen():e[this.prefix+("ms"===this.prefix?"RequestFullscreen":"RequestFullScreen")]()},e.cancelFullScreen=function(){return""===this.prefix?t.cancelFullScreen():t[this.prefix+("ms"===this.prefix?"ExitFullscreen":"CancelFullScreen")]()},e.element=function(){return""===this.prefix?t.fullscreenElement:t[this.prefix+"FullscreenElement"]}),e}function E(v,E){function A(e,t,n,r){h(e,t,n,x({},r,{plyr:Ue}))}function j(t,n){E.debug&&e.console&&(n=Array.prototype.slice.call(n),O.string(E.logPrefix)&&E.logPrefix.length&&n.unshift(E.logPrefix),console[t].apply(console,n))}function V(){return{url:E.iconUrl,absolute:0===E.iconUrl.indexOf("http")||Be.browser.isIE}}function R(){var e=[],t=V(),n=(t.absolute?"":t.url)+"#"+E.iconPrefix;return s(E.controls,"play-large")&&e.push('<button type="button" data-plyr="play" class="plyr__play-large">','<svg><use xlink:href="'+n+'-play" /></svg>','<span class="plyr__sr-only">'+E.i18n.play+"</span>","</button>"),e.push('<div class="plyr__controls">'),s(E.controls,"restart")&&e.push('<button type="button" data-plyr="restart">','<svg><use xlink:href="'+n+'-restart" /></svg>','<span class="plyr__sr-only">'+E.i18n.restart+"</span>","</button>"),s(E.controls,"rewind")&&e.push('<button type="button" data-plyr="rewind">','<svg><use xlink:href="'+n+'-rewind" /></svg>','<span class="plyr__sr-only">'+E.i18n.rewind+"</span>","</button>"),s(E.controls,"play")&&e.push('<button type="button" data-plyr="play">','<svg><use xlink:href="'+n+'-play" /></svg>','<span class="plyr__sr-only">'+E.i18n.play+"</span>","</button>",'<button type="button" data-plyr="pause">','<svg><use xlink:href="'+n+'-pause" /></svg>','<span class="plyr__sr-only">'+E.i18n.pause+"</span>","</button>"),s(E.controls,"fast-forward")&&e.push('<button type="button" data-plyr="fast-forward">','<svg><use xlink:href="'+n+'-fast-forward" /></svg>','<span class="plyr__sr-only">'+E.i18n.forward+"</span>","</button>"),s(E.controls,"progress")&&(e.push('<span class="plyr__progress">','<label for="seek{id}" class="plyr__sr-only">Seek</label>','<input id="seek{id}" class="plyr__progress--seek" type="range" min="0" max="100" step="0.1" value="0" data-plyr="seek">','<progress class="plyr__progress--played" max="100" value="0" role="presentation"></progress>','<progress class="plyr__progress--buffer" max="100" value="0">',"<span>0</span>% "+E.i18n.buffered,"</progress>"),E.tooltips.seek&&e.push('<span class="plyr__tooltip">00:00</span>'),e.push("</span>")),s(E.controls,"current-time")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+E.i18n.currentTime+"</span>",'<span class="plyr__time--current">00:00</span>',"</span>"),s(E.controls,"duration")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+E.i18n.duration+"</span>",'<span class="plyr__time--duration">00:00</span>',"</span>"),s(E.controls,"mute")&&e.push('<button type="button" data-plyr="mute">','<svg class="icon--muted"><use xlink:href="'+n+'-muted" /></svg>','<svg><use xlink:href="'+n+'-volume" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleMute+"</span>","</button>"),s(E.controls,"volume")&&e.push('<span class="plyr__volume">','<label for="volume{id}" class="plyr__sr-only">'+E.i18n.volume+"</label>",'<input id="volume{id}" class="plyr__volume--input" type="range" min="'+E.volumeMin+'" max="'+E.volumeMax+'" value="'+E.volume+'" data-plyr="volume">','<progress class="plyr__volume--display" max="'+E.volumeMax+'" value="'+E.volumeMin+'" role="presentation"></progress>',"</span>"),s(E.controls,"captions")&&e.push('<button type="button" data-plyr="captions">','<svg class="icon--captions-on"><use xlink:href="'+n+'-captions-on" /></svg>','<svg><use xlink:href="'+n+'-captions-off" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleCaptions+"</span>","</button>"),s(E.controls,"fullscreen")&&e.push('<button type="button" data-plyr="fullscreen">','<svg class="icon--exit-fullscreen"><use xlink:href="'+n+'-exit-fullscreen" /></svg>','<svg><use xlink:href="'+n+'-enter-fullscreen" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleFullscreen+"</span>","</button>"),e.push("</div>"),e.join("")}function q(){if(Be.supported.full&&("audio"!==Be.type||E.fullscreen.allowAudio)&&E.fullscreen.enabled){var e=N.supportsFullScreen;e||E.fullscreen.fallback&&!X()?(Je((e?"Native":"Fallback")+" fullscreen enabled"),m(Be.container,E.classes.fullscreen.enabled,!0)):Je("Fullscreen not supported and fallback disabled"),Be.buttons&&Be.buttons.fullscreen&&k(Be.buttons.fullscreen,!1),$()}}function D(){if("video"===Be.type){B(E.selectors.captions)||Be.videoContainer.insertAdjacentHTML("afterbegin",'<div class="'+p(E.selectors.captions)+'"></div>'),Be.usingTextTracks=!1,Be.media.textTracks&&(Be.usingTextTracks=!0);for(var e,t="",n=Be.media.childNodes,r=0;r<n.length;r++)"track"===n[r].nodeName.toLowerCase()&&(e=n[r].kind,"captions"!==e&&"subtitles"!==e||(t=n[r].getAttribute("src")));if(Be.captionExists=!0,""===t?(Be.captionExists=!1,Je("No caption track found")):Je("Caption track found; URI: "+t),Be.captionExists){for(var a=Be.media.textTracks,s=0;s<a.length;s++)a[s].mode="hidden";if(Y(Be),(Be.browser.isIE&&Be.browser.version>=10||Be.browser.isFirefox&&Be.browser.version>=31)&&(Je("Detected browser with known TextTrack issues - using manual fallback"),Be.usingTextTracks=!1),Be.usingTextTracks){Je("TextTracks supported");for(var o=0;o<a.length;o++){var i=a[o];"captions"!==i.kind&&"subtitles"!==i.kind||g(i,"cuechange",function(){this.activeCues[0]&&"text"in this.activeCues[0]?H(this.activeCues[0].getCueAsHTML()):H()})}}else if(Je("TextTracks not supported so rendering captions manually"),Be.currentCaption="",Be.captions=[],""!==t){var l=new XMLHttpRequest;l.onreadystatechange=function(){if(4===l.readyState)if(200===l.status){var e,t=[],n=l.responseText,r="\r\n";n.indexOf(r+r)===-1&&(r=n.indexOf("\r\r")!==-1?"\r":"\n"),t=n.split(r+r);for(var a=0;a<t.length;a++){e=t[a],Be.captions[a]=[];var s=e.split(r),o=0;s[o].indexOf(":")===-1&&(o=1),Be.captions[a]=[s[o],s[o+1]]}Be.captions.shift(),Je("Successfully loaded the caption file via AJAX")}else ze(E.logPrefix+"There was a problem loading the caption file via AJAX")},l.open("get",t,!0),l.send()}}else m(Be.container,E.classes.captions.enabled)}}function H(e){var n=B(E.selectors.captions),r=t.createElement("span");n.innerHTML="",O.undefined(e)&&(e=""),O.string(e)?r.innerHTML=e.trim():r.appendChild(e),n.appendChild(r);n.offsetHeight}function W(e){function t(e,t){var n=[];n=e.split(" --> ");for(var r=0;r<n.length;r++)n[r]=n[r].replace(/(\d+:\d+:\d+\.\d+).*/,"$1");return a(n[t])}function n(e){return t(e,0)}function r(e){return t(e,1)}function a(e){if(null===e||void 0===e)return 0;var t,n=[],r=[];return n=e.split(","),r=n[0].split(":"),t=Math.floor(60*r[0]*60)+Math.floor(60*r[1])+Math.floor(r[2])}if(!Be.usingTextTracks&&"video"===Be.type&&Be.supported.full&&(Be.subcount=0,e=O.number(e)?e:Be.media.currentTime,Be.captions[Be.subcount])){for(;r(Be.captions[Be.subcount][0])<e.toFixed(1);)if(Be.subcount++,Be.subcount>Be.captions.length-1){Be.subcount=Be.captions.length-1;break}Be.media.currentTime.toFixed(1)>=n(Be.captions[Be.subcount][0])&&Be.media.currentTime.toFixed(1)<=r(Be.captions[Be.subcount][0])?(Be.currentCaption=Be.captions[Be.subcount][1],H(Be.currentCaption)):H()}}function Y(){if(Be.buttons.captions){m(Be.container,E.classes.captions.enabled,!0);var e=Be.storage.captionsEnabled;O.boolean(e)||(e=E.captions.defaultActive),e&&(m(Be.container,E.classes.captions.active,!0),k(Be.buttons.captions,!0))}}function U(e){return Be.container.querySelectorAll(e)}function B(e){return U(e)[0]}function X(){try{return e.self!==e.top}catch(e){return!0}}function $(){function e(e){9===e.which&&Be.isFullscreen&&(e.target!==r||e.shiftKey?e.target===n&&e.shiftKey&&(e.preventDefault(),r.focus()):(e.preventDefault(),n.focus()))}var t=U("input:not([disabled]), button:not([disabled])"),n=t[0],r=t[t.length-1];g(Be.container,"keydown",e)}function J(e,t){if(O.string(t))d(e,Be.media,{src:t});else if(t.constructor===Array)for(var n=t.length-1;n>=0;n--)d(e,Be.media,t[n])}function z(){if(E.loadSprite){var e=V();e.absolute?(Je("AJAX loading absolute SVG sprite"+(Be.browser.isIE?" (due to IE)":"")),C(e.url,"sprite-plyr")):Je("Sprite will be used as external resource directly")}var n=E.html;Je("Injecting custom controls"),n||(n=R()),n=o(n,"{seektime}",E.seekTime),n=o(n,"{id}",Math.floor(1e4*Math.random()));var r;if(O.string(E.selectors.controls.container)&&(r=t.querySelector(E.selectors.controls.container)),O.htmlElement(r)||(r=Be.container),r.insertAdjacentHTML("beforeend",n),E.tooltips.controls)for(var a=U([E.selectors.controls.wrapper," ",E.selectors.labels," .",E.classes.hidden].join("")),s=a.length-1;s>=0;s--){var i=a[s];m(i,E.classes.hidden,!1),m(i,E.classes.tooltip,!0)}}function G(){try{return Be.controls=B(E.selectors.controls.wrapper),Be.buttons={},Be.buttons.seek=B(E.selectors.buttons.seek),Be.buttons.play=U(E.selectors.buttons.play),Be.buttons.pause=B(E.selectors.buttons.pause),Be.buttons.restart=B(E.selectors.buttons.restart),Be.buttons.rewind=B(E.selectors.buttons.rewind),Be.buttons.forward=B(E.selectors.buttons.forward),Be.buttons.fullscreen=B(E.selectors.buttons.fullscreen),Be.buttons.mute=B(E.selectors.buttons.mute),Be.buttons.captions=B(E.selectors.buttons.captions),Be.progress={},Be.progress.container=B(E.selectors.progress.container),Be.progress.buffer={},Be.progress.buffer.bar=B(E.selectors.progress.buffer),Be.progress.buffer.text=Be.progress.buffer.bar&&Be.progress.buffer.bar.getElementsByTagName("span")[0],Be.progress.played=B(E.selectors.progress.played),Be.progress.tooltip=Be.progress.container&&Be.progress.container.querySelector("."+E.classes.tooltip),Be.volume={},Be.volume.input=B(E.selectors.volume.input),Be.volume.display=B(E.selectors.volume.display),Be.duration=B(E.selectors.duration),Be.currentTime=B(E.selectors.currentTime),Be.seekTime=U(E.selectors.seekTime),!0}catch(e){return ze("It looks like there is a problem with your controls HTML"),Q(!0),!1}}function K(){m(Be.container,E.selectors.container.replace(".",""),Be.supported.full)}function Q(e){e&&s(E.types.html5,Be.type)?Be.media.setAttribute("controls",""):Be.media.removeAttribute("controls")}function Z(e){var t=E.i18n.play;if(O.string(E.title)&&E.title.length&&(t+=", "+E.title,Be.container.setAttribute("aria-label",E.title)),Be.supported.full&&Be.buttons.play)for(var n=Be.buttons.play.length-1;n>=0;n--)Be.buttons.play[n].setAttribute("aria-label",t);O.htmlElement(e)&&e.setAttribute("title",E.i18n.frameTitle.replace("{title}",E.title))}function ee(){var t=null;Be.storage={},L.supported&&E.storage.enabled&&(e.localStorage.removeItem("plyr-volume"),t=e.localStorage.getItem(E.storage.key),t&&(/^\d+(\.\d+)?$/.test(t)?te({volume:parseFloat(t)}):Be.storage=JSON.parse(t)))}function te(t){L.supported&&E.storage.enabled&&(x(Be.storage,t),e.localStorage.setItem(E.storage.key,JSON.stringify(Be.storage)))}function ne(){if(!Be.media)return void ze("No media element found!");if(Be.supported.full&&(m(Be.container,E.classes.type.replace("{0}",Be.type),!0),s(E.types.embed,Be.type)&&m(Be.container,E.classes.type.replace("{0}","video"),!0),m(Be.container,E.classes.stopped,E.autoplay),m(Be.container,E.classes.isIos,Be.browser.isIos),m(Be.container,E.classes.isTouch,Be.browser.isTouch),"video"===Be.type)){var e=t.createElement("div");e.setAttribute("class",E.classes.videoWrapper),i(Be.media,e),Be.videoContainer=e}s(E.types.embed,Be.type)&&re()}function re(){var n,r=t.createElement("div"),s=Be.type+"-"+Math.floor(1e4*Math.random());switch(Be.type){case"youtube":n=T(Be.embedId);break;case"vimeo":n=S(Be.embedId);break;default:n=Be.embedId}for(var o=U('[id^="'+Be.type+'-"]'),i=o.length-1;i>=0;i--)l(o[i]);if(m(Be.media,E.classes.videoWrapper,!0),m(Be.media,E.classes.embedWrapper,!0),"youtube"===Be.type)Be.media.appendChild(r),r.setAttribute("id",s),O.object(e.YT)?se(n,r):(a(E.urls.youtube.api),e.onYouTubeReadyCallbacks=e.onYouTubeReadyCallbacks||[],e.onYouTubeReadyCallbacks.push(function(){se(n,r)}),e.onYouTubeIframeAPIReady=function(){e.onYouTubeReadyCallbacks.forEach(function(e){e()})});else if("vimeo"===Be.type)if(Be.supported.full?Be.media.appendChild(r):r=Be.media,r.setAttribute("id",s),O.object(e.Vimeo))oe(n,r);else{a(E.urls.vimeo.api);var u=e.setInterval(function(){O.object(e.Vimeo)&&(e.clearInterval(u),oe(n,r))},50)}else if("soundcloud"===Be.type){var d=t.createElement("iframe");d.loaded=!1,g(d,"load",function(){d.loaded=!0}),c(d,{src:"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/"+n,id:s}),r.appendChild(d),Be.media.appendChild(r),e.SC||a(E.urls.soundcloud.api);var p=e.setInterval(function(){e.SC&&d.loaded&&(e.clearInterval(p),ie.call(d))},50)}}function ae(){Be.supported.full&&(We(),Ye()),Z(B("iframe"))}function se(t,n){Be.embed=new e.YT.Player(n.id,{videoId:t,playerVars:{autoplay:E.autoplay?1:0,controls:Be.supported.full?0:1,rel:0,showinfo:0,iv_load_policy:3,cc_load_policy:E.captions.defaultActive?1:0,cc_lang_pref:"en",wmode:"transparent",modestbranding:1,disablekb:1,origin:"*"},events:{onError:function(e){A(Be.container,"error",!0,{code:e.data,embed:e.target})},onReady:function(t){var n=t.target;Be.media.play=function(){n.playVideo(),Be.media.paused=!1},Be.media.pause=function(){n.pauseVideo(),Be.media.paused=!0},Be.media.stop=function(){n.stopVideo(),Be.media.paused=!0},Be.media.duration=n.getDuration(),Be.media.paused=!0,Be.media.currentTime=0,Be.media.muted=n.isMuted(),E.title=n.getVideoData().title,Be.supported.full&&Be.media.querySelector("iframe").setAttribute("tabindex","-1"),ae(),A(Be.media,"timeupdate"),A(Be.media,"durationchange"),e.clearInterval(Xe.buffering),Xe.buffering=e.setInterval(function(){Be.media.buffered=n.getVideoLoadedFraction(),(null===Be.media.lastBuffered||Be.media.lastBuffered<Be.media.buffered)&&A(Be.media,"progress"),Be.media.lastBuffered=Be.media.buffered,1===Be.media.buffered&&(e.clearInterval(Xe.buffering),A(Be.media,"canplaythrough"))},200)},onStateChange:function(t){var n=t.target;switch(e.clearInterval(Xe.playing),t.data){case 0:Be.media.paused=!0,A(Be.media,"ended");break;case 1:Be.media.paused=!1,Be.media.seeking&&A(Be.media,"seeked"),Be.media.seeking=!1,A(Be.media,"play"),A(Be.media,"playing"),Xe.playing=e.setInterval(function(){Be.media.currentTime=n.getCurrentTime(),A(Be.media,"timeupdate")},100),Be.media.duration!==n.getDuration()&&(Be.media.duration=n.getDuration(),A(Be.media,"durationchange"));break;case 2:Be.media.paused=!0,A(Be.media,"pause")}A(Be.container,"statechange",!1,{code:t.data})}}})}function oe(t,n){Be.embed=new e.Vimeo.Player(n,{id:parseInt(t),loop:E.loop,autoplay:E.autoplay,byline:!1,portrait:!1,title:!1}),Be.media.play=function(){Be.embed.play(),Be.media.paused=!1},Be.media.pause=function(){Be.embed.pause(),Be.media.paused=!0},Be.media.stop=function(){Be.embed.stop(),Be.media.paused=!0},Be.media.paused=!0,Be.media.currentTime=0,ae(),Be.embed.getCurrentTime().then(function(e){Be.media.currentTime=e,A(Be.media,"timeupdate")}),Be.embed.getDuration().then(function(e){Be.media.duration=e,A(Be.media,"durationchange")}),Be.embed.on("loaded",function(){O.htmlElement(Be.embed.element)&&Be.supported.full&&Be.embed.element.setAttribute("tabindex","-1")}),Be.embed.on("play",function(){Be.media.paused=!1,A(Be.media,"play"),A(Be.media,"playing")}),Be.embed.on("pause",function(){Be.media.paused=!0,A(Be.media,"pause")}),Be.embed.on("timeupdate",function(e){Be.media.seeking=!1,Be.media.currentTime=e.seconds,A(Be.media,"timeupdate")}),Be.embed.on("progress",function(e){Be.media.buffered=e.percent,A(Be.media,"progress"),1===parseInt(e.percent)&&A(Be.media,"canplaythrough")}),Be.embed.on("seeked",function(){Be.media.seeking=!1,A(Be.media,"seeked"),A(Be.media,"play")}),Be.embed.on("ended",function(){Be.media.paused=!0,A(Be.media,"ended")})}function ie(){Be.embed=e.SC.Widget(this),Be.embed.bind(e.SC.Widget.Events.READY,function(){Be.media.play=function(){Be.embed.play(),Be.media.paused=!1},Be.media.pause=function(){Be.embed.pause(),Be.media.paused=!0},Be.media.stop=function(){Be.embed.seekTo(0),Be.embed.pause(),Be.media.paused=!0},Be.media.paused=!0,Be.media.currentTime=0,Be.embed.getDuration(function(e){Be.media.duration=e/1e3,ae()}),Be.embed.getPosition(function(e){Be.media.currentTime=e,A(Be.media,"timeupdate")}),Be.embed.bind(e.SC.Widget.Events.PLAY,function(){Be.media.paused=!1,A(Be.media,"play"),A(Be.media,"playing")}),Be.embed.bind(e.SC.Widget.Events.PAUSE,function(){Be.media.paused=!0,A(Be.media,"pause")}),Be.embed.bind(e.SC.Widget.Events.PLAY_PROGRESS,function(e){Be.media.seeking=!1,Be.media.currentTime=e.currentPosition/1e3,A(Be.media,"timeupdate")}),Be.embed.bind(e.SC.Widget.Events.LOAD_PROGRESS,function(e){Be.media.buffered=e.loadProgress,A(Be.media,"progress"),1===parseInt(e.loadProgress)&&A(Be.media,"canplaythrough")}),Be.embed.bind(e.SC.Widget.Events.FINISH,function(){Be.media.paused=!0,A(Be.media,"ended")})})}function le(){"play"in Be.media&&Be.media.play()}function ue(){"pause"in Be.media&&Be.media.pause()}function ce(e){return O.boolean(e)||(e=Be.media.paused),e?le():ue(),e}function de(e){O.number(e)||(e=E.seekTime),me(Be.media.currentTime-e)}function pe(e){O.number(e)||(e=E.seekTime),me(Be.media.currentTime+e)}function me(e){var t=0,n=Be.media.paused,r=fe();O.number(e)?t=e:O.object(e)&&s(["input","change"],e.type)&&(t=e.target.value/e.target.max*r),t<0?t=0:t>r&&(t=r),Ne(t);try{Be.media.currentTime=t.toFixed(4)}catch(e){}if(s(E.types.embed,Be.type)){switch(Be.type){case"youtube":Be.embed.seekTo(t);break;case"vimeo":Be.embed.setCurrentTime(t.toFixed(0));break;case"soundcloud":Be.embed.seekTo(1e3*t)}n&&ue(),A(Be.media,"timeupdate"),Be.media.seeking=!0,A(Be.media,"seeking")}Je("Seeking to "+Be.media.currentTime+" seconds"),W(t)}function fe(){var e=parseInt(E.duration),t=0;return null===Be.media.duration||isNaN(Be.media.duration)||(t=Be.media.duration),isNaN(e)?t:e}function ye(){m(Be.container,E.classes.playing,!Be.media.paused),m(Be.container,E.classes.stopped,Be.media.paused),Me(Be.media.paused)}function be(){P={x:e.pageXOffset||0,y:e.pageYOffset||0}}function ve(){e.scrollTo(P.x,P.y)}function ge(e){var n=N.supportsFullScreen;if(n){if(!e||e.type!==N.fullScreenEventName)return N.isFullScreen(Be.container)?N.cancelFullScreen():(be(),N.requestFullScreen(Be.container)),void(Be.isFullscreen=N.isFullScreen(Be.container));Be.isFullscreen=N.isFullScreen(Be.container)}else Be.isFullscreen=!Be.isFullscreen,t.body.style.overflow=Be.isFullscreen?"hidden":"";m(Be.container,E.classes.fullscreen.active,Be.isFullscreen),$(Be.isFullscreen),Be.buttons&&Be.buttons.fullscreen&&k(Be.buttons.fullscreen,Be.isFullscreen),A(Be.container,Be.isFullscreen?"enterfullscreen":"exitfullscreen",!0),!Be.isFullscreen&&n&&ve()}function he(e){if(O.boolean(e)||(e=!Be.media.muted),k(Be.buttons.mute,e),Be.media.muted=e,0===Be.media.volume&&ke(E.volume),s(E.types.embed,Be.type)){switch(Be.type){case"youtube":Be.embed[Be.media.muted?"mute":"unMute"]();break;case"vimeo":case"soundcloud":Be.embed.setVolume(Be.media.muted?0:parseFloat(E.volume/E.volumeMax))}A(Be.media,"volumechange")}}function ke(e){var t=E.volumeMax,n=E.volumeMin;if(O.undefined(e)&&(e=Be.storage.volume),(null===e||isNaN(e))&&(e=E.volume),e>t&&(e=t),e<n&&(e=n),Be.media.volume=parseFloat(e/t),Be.volume.display&&(Be.volume.display.value=e),s(E.types.embed,Be.type)){switch(Be.type){case"youtube":Be.embed.setVolume(100*Be.media.volume);break;case"vimeo":case"soundcloud":Be.embed.setVolume(Be.media.volume)}A(Be.media,"volumechange")}0===e?Be.media.muted=!0:Be.media.muted&&e>0&&he()}function we(e){var t=Be.media.muted?0:Be.media.volume*E.volumeMax;O.number(e)||(e=E.volumeStep),ke(t+e)}function xe(e){var t=Be.media.muted?0:Be.media.volume*E.volumeMax;O.number(e)||(e=E.volumeStep),ke(t-e)}function Te(){var e=Be.media.muted?0:Be.media.volume*E.volumeMax;Be.supported.full&&(Be.volume.input&&(Be.volume.input.value=e),Be.volume.display&&(Be.volume.display.value=e)),te({volume:e}),m(Be.container,E.classes.muted,0===e),Be.supported.full&&Be.buttons.mute&&k(Be.buttons.mute,0===e)}function Se(e){Be.supported.full&&Be.buttons.captions&&(O.boolean(e)||(e=Be.container.className.indexOf(E.classes.captions.active)===-1),Be.captionsEnabled=e,k(Be.buttons.captions,Be.captionsEnabled),m(Be.container,E.classes.captions.active,Be.captionsEnabled),A(Be.container,Be.captionsEnabled?"captionsenabled":"captionsdisabled",!0),te({captionsEnabled:Be.captionsEnabled}))}function _e(e){var t="waiting"===e.type;clearTimeout(Xe.loading),Xe.loading=setTimeout(function(){m(Be.container,E.classes.loading,t),Me(t)},t?250:0)}function Ee(e){if(Be.supported.full){var t=Be.progress.played,n=0,r=fe();if(e)switch(e.type){case"timeupdate":case"seeking":if(Be.controls.pressed)return;n=w(Be.media.currentTime,r),"timeupdate"===e.type&&Be.buttons.seek&&(Be.buttons.seek.value=n);break;case"playing":case"progress":t=Be.progress.buffer,n=function(){var e=Be.media.buffered;return e&&e.length?w(e.end(0),r):O.number(e)?100*e:0}()}Ce(t,n)}}function Ce(e,t){if(Be.supported.full){if(O.undefined(t)&&(t=0),O.undefined(e)){if(!Be.progress||!Be.progress.buffer)return;e=Be.progress.buffer}O.htmlElement(e)?e.value=t:e&&(e.bar&&(e.bar.value=t),e.text&&(e.text.innerHTML=t))}}function Fe(e,t){if(t){isNaN(e)&&(e=0),Be.secs=parseInt(e%60),Be.mins=parseInt(e/60%60),Be.hours=parseInt(e/60/60%60);var n=parseInt(fe()/60/60%60)>0;Be.secs=("0"+Be.secs).slice(-2),Be.mins=("0"+Be.mins).slice(-2),t.innerHTML=(n?Be.hours+":":"")+Be.mins+":"+Be.secs}}function Ae(){if(Be.supported.full){var e=fe()||0;!Be.duration&&E.displayDuration&&Be.media.paused&&Fe(e,Be.currentTime),Be.duration&&Fe(e,Be.duration),Pe()}}function Ie(e){Fe(Be.media.currentTime,Be.currentTime),e&&"timeupdate"===e.type&&Be.media.seeking||Ee(e)}function Ne(e){O.number(e)||(e=0);var t=fe(),n=w(e,t);Be.progress&&Be.progress.played&&(Be.progress.played.value=n),Be.buttons&&Be.buttons.seek&&(Be.buttons.seek.value=n)}function Pe(e){var t=fe();if(E.tooltips.seek&&Be.progress.container&&0!==t){var n=Be.progress.container.getBoundingClientRect(),r=0,a=E.classes.tooltip+"--visible";if(e)r=100/n.width*(e.pageX-n.left);else{if(!f(Be.progress.tooltip,a))return;r=Be.progress.tooltip.style.left.replace("%","")}r<0?r=0:r>100&&(r=100),Fe(t/100*r,Be.progress.tooltip),Be.progress.tooltip.style.left=r+"%",e&&s(["mouseenter","mouseleave"],e.type)&&m(Be.progress.tooltip,a,"mouseenter"===e.type)}}function Me(t){if(E.hideControls&&"audio"!==Be.type){var n=0,r=!1,a=t,o=f(Be.container,E.classes.loading);if(O.boolean(t)||(t&&t.type?(r="enterfullscreen"===t.type,a=s(["mousemove","touchstart","mouseenter","focus"],t.type),s(["mousemove","touchmove"],t.type)&&(n=2e3),"focus"===t.type&&(n=3e3)):a=f(Be.container,E.classes.hideControls)),e.clearTimeout(Xe.hover),a||Be.media.paused||o){if(m(Be.container,E.classes.hideControls,!1),Be.media.paused||o)return;Be.browser.isTouch&&(n=3e3)}a&&Be.media.paused||(Xe.hover=e.setTimeout(function(){(!Be.controls.pressed&&!Be.controls.hover||r)&&m(Be.container,E.classes.hideControls,!0)},n))}}function Oe(e){if(!O.undefined(e))return void Le(e);var t;switch(Be.type){case"youtube":t=Be.embed.getVideoUrl();break;case"vimeo":Be.embed.getVideoUrl.then(function(e){t=e});break;case"soundcloud":Be.embed.getCurrentSound(function(e){t=e.permalink_url});break;default:t=Be.media.currentSrc}return t||""}function Le(e){function n(){if(Be.embed=null,l(Be.media),"video"===Be.type&&Be.videoContainer&&l(Be.videoContainer),Be.container&&Be.container.removeAttribute("class"),"type"in e&&(Be.type=e.type,"video"===Be.type)){var n=e.sources[0];"type"in n&&s(E.types.embed,n.type)&&(Be.type=n.type)}switch(Be.supported=F(Be.type),Be.type){case"video":Be.media=t.createElement("video");break;case"audio":Be.media=t.createElement("audio");break;case"youtube":case"vimeo":case"soundcloud":Be.media=t.createElement("div"),Be.embedId=e.sources[0].src}u(Be.container,Be.media),O.boolean(e.autoplay)&&(E.autoplay=e.autoplay),s(E.types.html5,Be.type)&&(E.crossorigin&&Be.media.setAttribute("crossorigin",""),E.autoplay&&Be.media.setAttribute("autoplay",""),"poster"in e&&Be.media.setAttribute("poster",e.poster),E.loop&&Be.media.setAttribute("loop","")),m(Be.container,E.classes.fullscreen.active,Be.isFullscreen),m(Be.container,E.classes.captions.active,Be.captionsEnabled),K(),s(E.types.html5,Be.type)&&J("source",e.sources),ne(),s(E.types.html5,Be.type)&&("tracks"in e&&J("track",e.tracks),Be.media.load()),(s(E.types.html5,Be.type)||s(E.types.embed,Be.type)&&!Be.supported.full)&&(We(),Ye()),E.title=e.title,Z()}return O.object(e)&&"sources"in e&&e.sources.length?(m(Be.container,E.classes.ready,!1),ue(),Ne(),Ce(),qe(),void De(n,!1)):void ze("Invalid source format")}function je(e){"video"===Be.type&&Be.media.setAttribute("poster",e)}function Ve(){function n(){var e=ce(),t=Be.buttons[e?"play":"pause"],n=Be.buttons[e?"pause":"play"];if(n=n&&n.length>1?n[n.length-1]:n[0]){var r=f(t,E.classes.tabFocus);setTimeout(function(){n.focus(),r&&(m(t,E.classes.tabFocus,!1),m(n,E.classes.tabFocus,!0))},100)}}function r(){var e=t.activeElement;return e=e&&e!==t.body?t.querySelector(":focus"):null}function a(e){return e.keyCode?e.keyCode:e.which}function o(e){for(var t in Be.buttons){var n=Be.buttons[t];if(O.nodeList(n))for(var r=0;r<n.length;r++)m(n[r],E.classes.tabFocus,n[r]===e);else m(n,E.classes.tabFocus,n===e)}}function i(e){function t(){var e=Be.media.duration;O.number(e)&&me(e/10*(n-48))}var n=a(e),r="keydown"===e.type,o=r&&n===u;if(O.number(n))if(r){var i=[48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67];switch(s(i,n)&&(e.preventDefault(),e.stopPropagation()),n){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:o||t();break;case 32:case 75:o||ce();break;case 38:we();break;case 40:xe();break;case 77:o||he();break;case 39:pe();break;case 37:de();break;case 70:ge();break;case 67:o||Se()}!N.supportsFullScreen&&Be.isFullscreen&&27===n&&ge(),u=n}else u=null}var l=Be.browser.isIE?"change":"input";if(E.keyboardShorcuts.focused){var u=null;E.keyboardShorcuts.global&&g(e,"keydown keyup",function(e){var t=a(e),n=r(),o=[48,49,50,51,52,53,54,56,57,75,77,70,67],l=I().length;1!==l||!s(o,t)||O.htmlElement(n)&&y(n,E.selectors.editable)||i(e)}),g(Be.container,"keydown keyup",i)}g(e,"keyup",function(e){var t=a(e),n=r();9===t&&o(n)}),g(t.body,"click",function(){m(B("."+E.classes.tabFocus),E.classes.tabFocus,!1)});for(var c in Be.buttons){var d=Be.buttons[c];g(d,"blur",function(){m(d,"tab-focus",!1);
-})}b(Be.buttons.play,"click",E.listeners.play,n),b(Be.buttons.pause,"click",E.listeners.pause,n),b(Be.buttons.restart,"click",E.listeners.restart,me),b(Be.buttons.rewind,"click",E.listeners.rewind,de),b(Be.buttons.forward,"click",E.listeners.forward,pe),b(Be.buttons.seek,l,E.listeners.seek,me),b(Be.volume.input,l,E.listeners.volume,function(){ke(Be.volume.input.value)}),b(Be.buttons.mute,"click",E.listeners.mute,he),b(Be.buttons.fullscreen,"click",E.listeners.fullscreen,ge),N.supportsFullScreen&&g(t,N.fullScreenEventName,ge),b(Be.buttons.captions,"click",E.listeners.captions,Se),g(Be.progress.container,"mouseenter mouseleave mousemove",Pe),E.hideControls&&(g(Be.container,"mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen",Me),g(Be.controls,"mouseenter mouseleave",function(e){Be.controls.hover="mouseenter"===e.type}),g(Be.controls,"mousedown mouseup touchstart touchend touchcancel",function(e){Be.controls.pressed=s(["mousedown","touchstart"],e.type)}),g(Be.controls,"focus blur",Me,!0)),g(Be.volume.input,"wheel",function(e){e.preventDefault();var t=e.webkitDirectionInvertedFromDevice,n=E.volumeStep/5;(e.deltaY<0||e.deltaX>0)&&(t?xe(n):we(n)),(e.deltaY>0||e.deltaX<0)&&(t?we(n):xe(n))})}function Re(){if(g(Be.media,"timeupdate seeking",Ie),g(Be.media,"timeupdate",W),g(Be.media,"durationchange loadedmetadata",Ae),g(Be.media,"ended",function(){"video"===Be.type&&E.showPosterOnEnd&&("video"===Be.type&&H(),me(),Be.media.load())}),g(Be.media,"progress playing",Ee),g(Be.media,"volumechange",Te),g(Be.media,"play pause ended",ye),g(Be.media,"waiting canplay seeked",_e),E.clickToPlay&&"audio"!==Be.type){var e=B("."+E.classes.videoWrapper);if(!e)return;e.style.cursor="pointer",g(e,"click",function(){E.hideControls&&Be.browser.isTouch&&!Be.media.paused||(Be.media.paused?le():Be.media.ended?(me(),le()):ue())})}E.disableContextMenu&&g(Be.media,"contextmenu",function(e){e.preventDefault()}),g(Be.media,E.events.concat(["keyup","keydown"]).join(" "),function(e){A(Be.container,e.type,!0)})}function qe(){if(s(E.types.html5,Be.type)){for(var e=Be.media.querySelectorAll("source"),t=0;t<e.length;t++)l(e[t]);Be.media.setAttribute("src",E.blankUrl),Be.media.load(),Je("Cancelled network requests")}}function De(n,r){function a(){clearTimeout(Xe.cleanUp),O.boolean(r)||(r=!0),O.function(n)&&n.call($e),r&&(Be.init=!1,Be.container.parentNode.replaceChild($e,Be.container),t.body.style.overflow="",A($e,"destroyed",!0))}if(!Be.init)return null;switch(Be.type){case"youtube":e.clearInterval(Xe.buffering),e.clearInterval(Xe.playing),Be.embed.destroy(),a();break;case"vimeo":Be.embed.unload().then(a),Xe.cleanUp=e.setTimeout(a,200);break;case"video":case"audio":Q(!0),a()}}function He(){if(Be.init)return null;if(N=_(),Be.browser=n(),O.htmlElement(Be.media)){ee();var e=v.tagName.toLowerCase();"div"===e?(Be.type=v.getAttribute("data-type"),Be.embedId=v.getAttribute("data-video-id"),v.removeAttribute("data-type"),v.removeAttribute("data-video-id")):(Be.type=e,E.crossorigin=null!==v.getAttribute("crossorigin"),E.autoplay=E.autoplay||null!==v.getAttribute("autoplay"),E.loop=E.loop||null!==v.getAttribute("loop")),Be.supported=F(Be.type),Be.supported.basic&&(Be.container=i(v,t.createElement("div")),Be.container.setAttribute("tabindex",0),K(),Je(""+Be.browser.name+" "+Be.browser.version),ne(),(s(E.types.html5,Be.type)||s(E.types.embed,Be.type)&&!Be.supported.full)&&(We(),Ye(),Z()),Be.init=!0)}}function We(){if(!Be.supported.full)return ze("Basic support only",Be.type),l(B(E.selectors.controls.wrapper)),l(B(E.selectors.buttons.play)),void Q(!0);var e=!U(E.selectors.controls.wrapper).length;e&&z(),G()&&(e&&Ve(),Re(),Q(),q(),D(),ke(),Te(),Ie(),ye())}function Ye(){e.setTimeout(function(){A(Be.media,"ready")},0),m(Be.media,M.classes.setup,!0),m(Be.container,E.classes.ready,!0),Be.media.plyr=Ue,E.autoplay&&le()}var Ue,Be=this,Xe={};Be.media=v;var $e=v.cloneNode(!0),Je=function(){j("log",arguments)},ze=function(){j("warn",arguments)};return Je("Config",E),Ue={getOriginal:function(){return $e},getContainer:function(){return Be.container},getEmbed:function(){return Be.embed},getMedia:function(){return Be.media},getType:function(){return Be.type},getDuration:fe,getCurrentTime:function(){return Be.media.currentTime},getVolume:function(){return Be.media.volume},isMuted:function(){return Be.media.muted},isReady:function(){return f(Be.container,E.classes.ready)},isLoading:function(){return f(Be.container,E.classes.loading)},isPaused:function(){return Be.media.paused},on:function(e,t){return g(Be.container,e,t),this},play:le,pause:ue,stop:function(){ue(),me()},restart:me,rewind:de,forward:pe,seek:me,source:Oe,poster:je,setVolume:ke,togglePlay:ce,toggleMute:he,toggleCaptions:Se,toggleFullscreen:ge,toggleControls:Me,isFullscreen:function(){return Be.isFullscreen||!1},support:function(e){return r(Be,e)},destroy:De},He(),Be.init?Ue:null}function C(e,n){var r=new XMLHttpRequest;if(!O.string(n)||!O.htmlElement(t.querySelector("#"+n))){var a=t.createElement("div");a.setAttribute("hidden",""),O.string(n)&&a.setAttribute("id",n),t.body.insertBefore(a,t.body.childNodes[0]),"withCredentials"in r&&(r.open("GET",e,!0),r.onload=function(){a.innerHTML=r.responseText},r.send())}}function F(e){var r=n(),a=r.isIE&&r.version<=9,s=r.isIos,o=r.isIphone,i=!!t.createElement("audio").canPlayType,l=!!t.createElement("video").canPlayType,u=!1,c=!1;switch(e){case"video":u=l,c=u&&!a&&!o;break;case"audio":u=i,c=u&&!a;break;case"vimeo":u=!0,c=!a&&!s;break;case"youtube":u=!0,c=!a&&!s,s&&!o&&r.version>=10&&(c=!0);break;case"soundcloud":u=!0,c=!a&&!o;break;default:u=i&&l,c=u&&!a}return{basic:u,full:c}}function A(e,n){function r(e,t){f(t,M.classes.hook)||a.push({target:e,media:t})}var a=[],s=[],o=[M.selectors.html5,M.selectors.embed].join(",");if(O.string(e)?e=t.querySelectorAll(e):O.htmlElement(e)?e=[e]:O.nodeList(e)||O.array(e)||O.string(e)||(O.undefined(n)&&O.object(e)&&(n=e),e=t.querySelectorAll(o)),O.nodeList(e)&&(e=Array.prototype.slice.call(e)),!F().basic||!e.length)return!1;for(var i=0;i<e.length;i++){var l=e[i],u=l.querySelectorAll(o);if(u.length)for(var c=0;c<u.length;c++)r(l,u[c]);else y(l,o)&&r(l,l)}return a.forEach(function(e){var t=e.target,r=e.media,a=!1;r===t&&(a=!0);var o={};try{o=JSON.parse(t.getAttribute("data-plyr"))}catch(e){}var i=x({},M,n,o);if(!i.enabled)return null;var l=new E(r,i);if(O.object(l)){if(i.debug){var u=i.events.concat(["setup","statechange","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled"]);g(l.getContainer(),u.join(" "),function(e){console.log([i.logPrefix,"event:",e.type].join(" "),e.detail.plyr)})}h(l.getContainer(),"setup",!0,{plyr:l}),s.push(l)}}),s}function I(e){if(O.string(e)?e=t.querySelector(e):O.undefined(e)&&(e=t.body),O.htmlElement(e)){var n=e.querySelectorAll("."+M.classes.setup),r=[];return Array.prototype.slice.call(n).forEach(function(e){O.object(e.plyr)&&r.push(e.plyr)}),r}return[]}var N,P={x:0,y:0},M={enabled:!0,debug:!1,autoplay:!1,loop:!1,seekTime:10,volume:10,volumeMin:0,volumeMax:10,volumeStep:1,duration:null,displayDuration:!0,loadSprite:!0,iconPrefix:"plyr",iconUrl:"https://cdn.plyr.io/2.0.11/plyr.svg",blankUrl:"https://cdn.selz.com/plyr/blank.mp4",clickToPlay:!0,hideControls:!0,showPosterOnEnd:!1,disableContextMenu:!0,keyboardShorcuts:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},selectors:{html5:"video, audio",embed:"[data-type]",editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{seek:'[data-plyr="seek"]',play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',forward:'[data-plyr="fast-forward"]',mute:'[data-plyr="mute"]',captions:'[data-plyr="captions"]',fullscreen:'[data-plyr="fullscreen"]'},volume:{input:'[data-plyr="volume"]',display:".plyr__volume--display"},progress:{container:".plyr__progress",buffer:".plyr__progress--buffer",played:".plyr__progress--played"},captions:".plyr__captions",currentTime:".plyr__time--current",duration:".plyr__time--duration"},classes:{setup:"plyr--setup",ready:"plyr--ready",videoWrapper:"plyr__video-wrapper",embedWrapper:"plyr__video-embed",type:"plyr--{0}",stopped:"plyr--stopped",playing:"plyr--playing",muted:"plyr--muted",loading:"plyr--loading",hover:"plyr--hover",tooltip:"plyr__tooltip",hidden:"plyr__sr-only",hideControls:"plyr--hide-controls",isIos:"plyr--is-ios",isTouch:"plyr--is-touch",captions:{enabled:"plyr--captions-enabled",active:"plyr--captions-active"},fullscreen:{enabled:"plyr--fullscreen-enabled",active:"plyr--fullscreen-active"},tabFocus:"tab-focus"},captions:{defaultActive:!1},fullscreen:{enabled:!0,fallback:!0,allowAudio:!1},storage:{enabled:!0,key:"plyr"},controls:["play-large","play","progress","current-time","mute","volume","captions","fullscreen"],i18n:{restart:"Restart",rewind:"Rewind {seektime} secs",play:"Play",pause:"Pause",forward:"Forward {seektime} secs",played:"played",buffered:"buffered",currentTime:"Current time",duration:"Duration",volume:"Volume",toggleMute:"Toggle Mute",toggleCaptions:"Toggle Captions",toggleFullscreen:"Toggle Fullscreen",frameTitle:"Player for {title}"},types:{embed:["youtube","vimeo","soundcloud"],html5:["video","audio"]},urls:{vimeo:{api:"https://player.vimeo.com/api/player.js"},youtube:{api:"https://www.youtube.com/iframe_api"},soundcloud:{api:"https://w.soundcloud.com/player/api.js"}},listeners:{seek:null,play:null,pause:null,restart:null,rewind:null,forward:null,mute:null,volume:null,captions:null,fullscreen:null},events:["ready","ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied"],logPrefix:"[Plyr]"},O={object:function(e){return null!==e&&"object"==typeof e},array:function(e){return null!==e&&"object"==typeof e&&e.constructor===Array},number:function(e){return null!==e&&("number"==typeof e&&!isNaN(e-0)||"object"==typeof e&&e.constructor===Number)},string:function(e){return null!==e&&("string"==typeof e||"object"==typeof e&&e.constructor===String)},boolean:function(e){return null!==e&&"boolean"==typeof e},nodeList:function(e){return null!==e&&e instanceof NodeList},htmlElement:function(e){return null!==e&&e instanceof HTMLElement},function:function(e){return null!==e&&"function"==typeof e},undefined:function(e){return null!==e&&"undefined"==typeof e}},L={supported:function(){if(!("localStorage"in e))return!1;try{e.localStorage.setItem("___test","OK");var t=e.localStorage.getItem("___test");return e.localStorage.removeItem("___test"),"OK"===t}catch(e){return!1}return!1}()};return{setup:A,supported:F,loadSprite:C,get:I}}),function(){function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),n}"function"!=typeof window.CustomEvent&&(e.prototype=window.Event.prototype,window.CustomEvent=e)}(); \ No newline at end of file
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t(e,document):"function"==typeof define&&define.amd?define([],function(){return t(e,document)}):e.plyr=t(e,document)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(){var e,n,a,s=navigator.userAgent,l=navigator.appName,i=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10),o=!1,u=!1,c=!1,d=!1;return navigator.appVersion.indexOf("Windows NT")!==-1&&navigator.appVersion.indexOf("rv:11")!==-1?(o=!0,l="IE",i="11"):(n=s.indexOf("MSIE"))!==-1?(o=!0,l="IE",i=s.substring(n+5)):(n=s.indexOf("Chrome"))!==-1?(c=!0,l="Chrome",i=s.substring(n+7)):(n=s.indexOf("Safari"))!==-1?(d=!0,l="Safari",i=s.substring(n+7),(n=s.indexOf("Version"))!==-1&&(i=s.substring(n+8))):(n=s.indexOf("Firefox"))!==-1?(u=!0,l="Firefox",i=s.substring(n+8)):(e=s.lastIndexOf(" ")+1)<(n=s.lastIndexOf("/"))&&(l=s.substring(e,n),i=s.substring(n+1),l.toLowerCase()===l.toUpperCase()&&(l=navigator.appName)),(a=i.indexOf(";"))!==-1&&(i=i.substring(0,a)),(a=i.indexOf(" "))!==-1&&(i=i.substring(0,a)),r=parseInt(""+i,10),isNaN(r)&&(i=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),{name:l,version:r,isIE:o,isFirefox:u,isChrome:c,isSafari:d,isIos:/(iPad|iPhone|iPod)/g.test(navigator.platform),isTouch:"ontouchstart"in t.documentElement}}function a(e){if(!t.querySelectorAll('script[src="'+e+'"]').length){var n=t.createElement("script");n.src=e;var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(n,a)}}function s(){try{return e.self!==e.top}catch(e){return!0}}function l(e,t){return Array.prototype.indexOf&&e.indexOf(t)!==-1}function i(e,t){e.length||(e=[e]);for(var n=e.length-1;n>=0;n--){var a=n>0?t.cloneNode(!0):t,s=e[n],l=s.parentNode,i=s.nextSibling;return a.appendChild(s),i?l.insertBefore(a,i):l.appendChild(a),a}}function r(e){e&&e.parentNode.removeChild(e)}function o(e,t){e.insertBefore(t,e.firstChild)}function u(e,t){for(var n in t)e.setAttribute(n,t[n])}function c(e,t){if(!j.string(e)||j.empty(e))return{};var n={};return e.split(",").forEach(function(e){e=e.trim();var a=e.charAt(0);switch(a){case".":var s=e.replace(".","");j.object(t)&&j.string(t.class)&&(t.class+=" "+s),n.class=s;break;case"#":n.id=e.replace("#","");break;case"[":e=e.replace(/[\[\]]/g,"");var l=e.split("="),i=l[0],r=l.length>1?l[1].replace(/[\"\']/g,""):"";n[i]=r}}),n}function d(e,n,a){var s=t.createElement(e);return j.object(n)&&u(s,n),j.string(a)&&(s.textContent=a),s}function p(e,t,n,a){var s=d(e,n,a);o(t,s)}function m(e){for(var t=e.childNodes.length;t--;)e.removeChild(e.lastChild)}function f(e){return e.replace(".","")}function y(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var a=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=a+(n?" "+t:"")}}function b(e,t){return!!e&&(e.classList?e.classList.contains(t):new RegExp("(\\s|^)"+t+"(\\s|$)").test(e.className))}function v(e,n){var a=Element.prototype,s=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.msMatchesSelector||function(e){return[].indexOf.call(t.querySelectorAll(e),this)!==-1};return s.call(e,n)}function g(){var e=t.activeElement;return e=e&&e!==t.body?t.querySelector(":focus"):null}function h(e,t,n,a,s){w(e,t,function(t){n&&n.apply(e,[t]),a.apply(e,[t])},s)}function k(e,t,n,a,s){var l=t.split(" ");if(j.boolean(s)||(s=!1),e instanceof NodeList)for(var i=0;i<e.length;i++)e[i]instanceof Node&&k(e[i],arguments[1],arguments[2],arguments[3]);else for(var r=0;r<l.length;r++)e[a?"addEventListener":"removeEventListener"](l[r],n,s)}function w(e,t,n,a){j.undefined(e)||k(e,t,n,!0,a)}function C(e,t,n,a){j.undefined(e)||k(e,t,n,!1,a)}function x(e,t,n,a){if(e&&t){j.boolean(n)||(n=!1);var s=new CustomEvent(t,{bubbles:n,detail:a});e.dispatchEvent(s)}}function T(e,t){if(e)return t=j.boolean(t)?t:!e.getAttribute("aria-pressed"),e.setAttribute("aria-pressed",t),t}function S(e,t){return 0===e||0===t||isNaN(e)||isNaN(t)?0:(e/t*100).toFixed(2)}function E(){var e=arguments;if(e.length){if(1===e.length)return e[0];var t=Array.prototype.shift.call(e);j.object(t)||(t={});for(var n=e.length,a=0;a<n;a++){var s=e[a];j.object(s)||(s={});for(var l in s)s[l]&&s[l].constructor&&s[l].constructor===Object?(t[l]=t[l]||{},E(t[l],s[l])):t[l]=s[l]}return t}}function A(e){var t=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;return e.match(t)?RegExp.$2:e}function _(e){var t=/^.*(vimeo.com\/|video\/)(\d+).*/;return e.match(t)?RegExp.$2:e}function I(k,I){function F(t,n){I.debug&&e.console&&(n=Array.prototype.slice.call(n),j.string(I.logPrefix)&&I.logPrefix.length&&n.unshift(I.logPrefix),e.console[t].apply(e.console,n))}function V(e,t,n,a){x(e,t,n,E({},a,{plyr:it}))}function D(e){return rt.elements.container.querySelectorAll(e)}function W(e){return D(e)[0]}function H(){function e(e){9===e.which&&rt.fullscreen.active&&(e.target!==a||e.shiftKey?e.target===n&&e.shiftKey&&(e.preventDefault(),a.focus()):(e.preventDefault(),n.focus()))}var t=D("input:not([disabled]), button:not([disabled])"),n=t[0],a=t[t.length-1];w(rt.elements.container,"keydown",e)}function B(e,t){j.string(t)?p(e,rt.elements.media,{src:t}):j.array(t)&&t.forEach(function(t){p(e,rt.elements.media,t)})}function Y(){return{url:I.iconUrl,absolute:0===I.iconUrl.indexOf("http")||rt.browser.isIE}}function X(e,n){var a="http://www.w3.org/2000/svg",s=Y(),l=(s.absolute?"":s.url)+"#"+I.iconPrefix,i=t.createElementNS(a,"svg");u(i,E(n,{role:"presentation"}));var r=t.createElementNS(a,"use");return r.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l+"-"+e),i.appendChild(r),i}function U(e){var t=I.i18n[e];switch(e){case"pip":t="PIP";break;case"airplay":t="AirPlay"}return d("span",{class:I.classes.hidden},t)}function J(e){var t=d("span",{class:I.classes.menu.value});return t.appendChild(d("span",{class:I.classes.menu.badge},e)),t}function K(e,t){var n,a,s,l=d("button");switch(j.object(t)||(t={}),"class"in t?t.class.indexOf(I.classes.control)===-1&&(t.class+=" "+I.classes.control):t.class=I.classes.control,e){case"mute":s="toggleMute",n="volume",a="muted";break;case"captions":s="toggleCaptions",n="captions-off",a="captions-on";break;case"fullscreen":s="toggleFullscreen",n="enter-fullscreen",a="exit-fullscreen";break;case"play-large":t.class="plyr__play-large",e="play",s="play",n="play";break;default:s=e,n=e}return E(t,c(I.selectors.buttons[e],t)),j.string(a)&&l.appendChild(X(a,{class:"icon--"+a})),l.appendChild(X(n)),l.appendChild(U(s)),u(l,t),rt.elements.buttons[e]=l,l}function Q(e,t){var n=d("label",{for:t.id,class:I.classes.hidden},I.i18n[e]),a=d("input",E(c(I.selectors.inputs[e]),{type:"range",min:0,max:100,step:.1,value:0},t));return rt.elements.inputs[e]=a,{label:n,input:a}}function z(e,t){var n=d("progress",E(c(I.selectors.display[e]),{min:0,max:100,value:0},t));if("volume"!==e){n.appendChild(d("span",null,"0"));var a="";switch(e){case"played":a=I.i18n.played;break;case"buffer":a=I.i18n.buffered}n.textContent="% "+a.toLowerCase()}return rt.elements.display[e]=n,n}function $(e){var t=d("span",{class:"plyr__time"});return t.appendChild(d("span",{class:I.classes.hidden},I.i18n[e])),t.appendChild(d("span",c(I.selectors.display[e]),"00:00")),rt.elements.display[e]=t,t}function G(e){var t=d("div",c(I.selectors.controls.wrapper));if(l(I.controls,"restart")&&t.appendChild(K("restart")),l(I.controls,"rewind")&&t.appendChild(K("rewind")),l(I.controls,"play")&&(t.appendChild(K("play")),t.appendChild(K("pause"))),l(I.controls,"fast-forward")&&t.appendChild(K("fast-forward")),l(I.controls,"progress")){var n=d("span",c(I.selectors.progress)),a=Q("seek",{id:"plyr-seek-"+e.id});if(n.appendChild(a.label),n.appendChild(a.input),n.appendChild(z("played")),n.appendChild(z("buffer")),I.tooltips.seek){var s=d("span",{role:"tooltip",class:I.classes.tooltip},"00:00");n.appendChild(s),rt.elements.display.seekTooltip=s}rt.elements.progress=n,t.appendChild(rt.elements.progress)}if(l(I.controls,"current-time")&&t.appendChild($("currentTime")),l(I.controls,"duration")&&t.appendChild($("duration")),l(I.controls,"mute")&&t.appendChild(K("mute")),l(I.controls,"volume")){var i=d("span",{class:"plyr__volume"}),r={max:10,value:I.volume},o=Q("volume",E(r,{id:"plyr-volume-"+e.id}));i.appendChild(o.label),i.appendChild(o.input);var u=z("volume",r);i.appendChild(u),t.appendChild(i)}if(l(I.controls,"captions")&&t.appendChild(K("captions")),l(I.controls,"settings")){var p=d("span",E(c(I.selectors.buttons.settings),{class:"plyr__menu"}));p.appendChild(K("settings",{id:"plyr-settings-toggle-"+e.id,"aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id,"aria-expanded":!1}));var m=d("form",{class:"plyr__menu__container",id:"plyr-settings-"+e.id,"aria-hidden":!0,"aria-labelled-by":"plyr-settings-toggle-"+e.id,role:"tablist",tabindex:-1}),f=d("div"),y=d("div",{id:"plyr-settings-"+e.id+"-home","aria-hidden":!1,"aria-labelled-by":"plyr-settings-toggle-"+e.id,role:"tabpanel",tabindex:-1}),b=d("ul",{role:"tablist"});["captions","quality","speed","loop"].forEach(function(t){var n=d("li",{role:"tab"}),a=d("button",E(c(I.selectors.buttons.settings),{type:"button",class:I.classes.control+" "+I.classes.control+"--forward",id:"plyr-settings-"+e.id+"-"+t+"-tab","aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id+"-"+t,"aria-expanded":!1}),I.i18n[t]),s=d("span",{class:I.classes.menu.value});s.innerHTML=e[t],a.appendChild(s),n.appendChild(a),b.appendChild(n),rt.elements.settings.tabs[t]=n}),y.appendChild(b),f.appendChild(y),["captions","quality","speed","loop"].forEach(function(t){var n=d("div",{id:"plyr-settings-"+e.id+"-"+t,"aria-hidden":!0,"aria-labelled-by":"plyr-settings-tab-"+e.id,role:"tabpanel",tabindex:-1}),a=d("button",{type:"button",class:I.classes.control+" "+I.classes.control+"--back","aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id+"-home","aria-expanded":!1},I.i18n[t]);n.appendChild(a);var s=d("ul");n.appendChild(s),f.appendChild(n),rt.elements.settings.panes[t]=n}),m.appendChild(f),p.appendChild(m),t.appendChild(p),rt.elements.settings.menu=p}return l(I.controls,"pip")&&R.pip&&t.appendChild(K("pip")),l(I.controls,"airplay")&&R.airplay&&t.appendChild(K("airplay")),l(I.controls,"fullscreen")&&t.appendChild(K("fullscreen")),rt.elements.controls=t,ee(),t}function Z(e,n){function a(e){var t="";switch(e){case"hd2160":t="4K";break;case"hd1440":t="WQHD";break;case"hd1080":t="HD";break;case"hd720":t="HD"}return t.length?J(t):null}function s(e){switch(e){case"hd2160":return"2160P";case"hd1440":return"1440P";case"hd1080":return"1080P";case"hd720":return"720P";case"large":return"480P";case"medium":return"360P";case"small":return"240P";default:return"Auto"}}if(!j.object(rt.quality)&&(rt.quality={available:e,current:n},j.array(e)&&e.length)){var l=e.filter(function(e){return["tiny","small"].indexOf(e)===-1}),i=rt.elements.settings.panes.quality.querySelector("ul");l.forEach(function(e){var n=d("li"),l=d("label",{class:I.classes.control}),r=d("input",{type:"radio",name:"quality",value:e});e===rt.quality.current&&r.setAttribute("checked",""),l.appendChild(r),l.appendChild(t.createTextNode(s(e)));var o=a(e);j.htmlElement(o)&&l.appendChild(o),n.appendChild(l),i.appendChild(n)})}}function ee(){var e=["start","end","all","reset"],t=rt.elements.settings.panes.loop.querySelector("ul");e.forEach(function(e){var n=d("li"),a=d("button",{type:"button",class:I.classes.control,"data-plyr":"loop","data-plyr-loop-action":e},I.i18n[e]);if(l(["start","end"],e)){var s=J("0:00");a.appendChild(s)}n.appendChild(a),t.appendChild(n)})}function te(){if(rt.supported.full&&("audio"!==rt.type||I.fullscreen.allowAudio)&&I.fullscreen.enabled){var e=R.fullscreen;e||I.fullscreen.fallback&&!s()?(ct((e?"Native":"Fallback")+" fullscreen enabled"),y(rt.elements.container,I.classes.fullscreen.enabled,!0)):ct("Fullscreen not supported and fallback disabled"),rt.elements.buttons&&rt.elements.buttons.fullscreen&&T(rt.elements.buttons.fullscreen,!1),H()}}function ne(e){j.event(e)&&(e=e.target),e.activeCues[0]&&"text"in e.activeCues[0]?le(e.activeCues[0].getCueAsHTML()):le()}function ae(){if("video"===rt.type){W(I.selectors.captions)||rt.elements.wrapper.insertAdjacentHTML("afterbegin",'<div class="'+f(I.selectors.captions)+'"></div>'),rt.captions.textTracks=!1,rt.elements.media.textTracks&&(rt.captions.textTracks=!0);var e=[],t="";if(rt.elements.media.childNodes.forEach(function(t){"track"===t.nodeName.toLowerCase()&&("captions"!==t.kind&&"subtitles"!==t.kind||e.push(t.getAttribute("src")))}),rt.captions.exist=!0,0===e.length?(rt.captions.exist=!1,ct("No caption track found")):Number(I.captions.selectedIndex)+1>e.length?(rt.captions.exist=!1,ct("Caption index out of bound")):(t=e[I.captions.selectedIndex],ct("Caption track found; URI: "+t)),rt.captions.exist){var n=rt.elements.media.textTracks;if([].forEach.call(n,function(e){C(e,"cuechange",ne),e.mode="hidden"}),re(rt),(rt.browser.isIE&&rt.browser.version>=10||rt.browser.isFirefox&&rt.browser.version>=31)&&(ct("Detected browser with known TextTrack issues - using manual fallback"),rt.captions.textTracks=!1),rt.captions.textTracks){ct("TextTracks supported");var a=n[I.captions.selectedIndex];"captions"!==a.kind&&"subtitles"!==a.kind||(w(a,"cuechange",ne),a.activeCues&&a.activeCues.length>0&&ne(a))}else if(ct("TextTracks not supported so rendering captions manually"),rt.captions.current="",rt.captions.captions=[],""!==t){var s=new XMLHttpRequest;s.onreadystatechange=function(){if(4===s.readyState)if(200===s.status){var e=s.responseText,t="\r\n";e.indexOf(t+t)===-1&&(t=e.indexOf("\r\r")!==-1?"\r":"\n");var n=e.split(t+t);rt.captions.captions=n.map(function(e){var n=e.split(t),a=0;return n[a].indexOf(":")!==-1&&(a=1),[n[a],n[a+1]]}),rt.captions.captions.shift(),ct("Successfully loaded the caption file via AJAX")}else dt(I.logPrefix+"There was a problem loading the caption file via AJAX")},s.open("get",t,!0),s.send()}}else y(rt.elements.container,I.classes.captions.enabled)}}function se(e){I.captions.selectedIndex=e||I.captions.selectedIndex,le(),ae()}function le(e){var t=W(I.selectors.captions);if(j.htmlElement(t)){var n=d("span");m(t),j.undefined(e)&&(e=""),j.string(e)?n.innerHTML=e.trim():n.appendChild(e),t.appendChild(n)}}function ie(e){function t(e,t){var n=[];n=e.split(" --> ");for(var a=0;a<n.length;a++)n[a]=n[a].replace(/(\d+:\d+:\d+\.\d+).*/,"$1");return s(n[t])}function n(e){return t(e,0)}function a(e){return t(e,1)}function s(e){if(j.undefined(e))return 0;var t=[],n=[],a=0;t=e.split(","),n=t[0].split(":");for(var s=0,l=n.length;s<l;s++)a+=Math.floor(n[s]*Math.pow(60,l-(s+1)));return a}if(!rt.captions.textTracks&&"video"===rt.type&&rt.supported.full&&(rt.captions.count=0,e=j.number(e)?e:rt.elements.media.currentTime,rt.captions.captions[rt.captions.count])){for(;a(rt.captions.captions[rt.captions.count][0])<e.toFixed(1);)if(rt.captions.count++,rt.captions.count>rt.captions.captions.length-1){rt.captions.count=rt.captions.captions.length-1;break}rt.elements.media.currentTime.toFixed(1)>=n(rt.captions[rt.subcount][0])&&rt.elements.media.currentTime.toFixed(1)<=a(rt.captions[rt.subcount][0])?(rt.captions.current=rt.captions.captions[rt.captions.count][1],le(rt.captions.current)):le()}}function re(){if(rt.elements.buttons.captions){y(rt.elements.container,I.classes.captions.enabled,!0);var e=rt.storage.captions;j.boolean(e)||(e=I.captions.defaultActive),e&&(y(rt.elements.container,I.classes.captions.active,!0),T(rt.elements.buttons.captions,!0))}}function oe(e){rt.supported.full&&rt.elements.buttons.captions&&(j.boolean(e)||(e=rt.elements.container.className.indexOf(I.classes.captions.active)===-1),rt.captions.enabled=e,T(rt.elements.buttons.captions,rt.captions.enabled),y(rt.elements.container,I.classes.captions.active,rt.captions.enabled),V(rt.elements.container,rt.captions.enabled?"captionsenabled":"captionsdisabled",!0),fe({captions:rt.captions.enabled}))}function ue(){if(I.loadSprite){var e=Y();e.absolute?(ct("AJAX loading absolute SVG sprite"+(rt.browser.isIE?" (due to IE)":"")),P(e.url,"sprite-plyr")):ct("Sprite will be used as external resource directly")}l(I.controls,"play-large")&&(rt.elements.buttons.playLarge=K("play-large"),rt.elements.container.appendChild(rt.elements.buttons.playLarge)),rt.id=Math.floor(1e4*Math.random());var n,a=G({id:rt.id,seektime:I.seekTime,speed:Ee(),quality:"HD",captions:"English",loop:"None"});if(j.string(I.selectors.controls.container)&&(n=t.querySelector(I.selectors.controls.container)),j.htmlElement(n)||(n=rt.elements.container),n.appendChild(a),I.tooltips.controls)for(var s=D([I.selectors.controls.wrapper," ",I.selectors.labels," .",I.classes.hidden].join("")),i=s.length-1;i>=0;i--){var r=s[i];y(r,I.classes.hidden,!1),y(r,I.classes.tooltip,!0)}}function ce(){y(rt.elements.container,I.selectors.container.replace(".",""),rt.supported.full)}function de(e){e&&l(I.types.html5,rt.type)?rt.elements.media.setAttribute("controls",""):rt.elements.media.removeAttribute("controls")}function pe(e){var t=I.i18n.play;j.string(I.title)&&I.title.length&&(t+=", "+I.title,rt.elements.container.setAttribute("aria-label",I.title)),rt.supported.full&&(j.htmlElement(rt.elements.buttons.play)&&rt.elements.buttons.play.setAttribute("aria-label",t),j.htmlElement(rt.elements.buttons.playLarge)&&rt.elements.buttons.playLarge.setAttribute("aria-label",t)),j.htmlElement(e)&&e.setAttribute("title",I.i18n.frameTitle.replace("{title}",I.title))}function me(){var t=null;rt.storage={},R.storage&&I.storage.enabled&&(e.localStorage.removeItem("plyr-volume"),t=e.localStorage.getItem(I.storage.key),t&&(/^\d+(\.\d+)?$/.test(t)?fe({volume:parseFloat(t)}):rt.storage=JSON.parse(t)))}function fe(t){R.storage&&I.storage.enabled&&(E(rt.storage,t),e.localStorage.setItem(I.storage.key,JSON.stringify(rt.storage)))}function ye(){if(!rt.elements.media)return void dt("No media element found!");if(rt.supported.full&&(y(rt.elements.container,I.classes.type.replace("{0}",rt.type),!0),l(I.types.embed,rt.type)&&y(rt.elements.container,I.classes.type.replace("{0}","video"),!0),y(rt.elements.container,I.classes.pip.enabled,R.pip&&"video"===rt.type),y(rt.elements.container,I.classes.airplay.enabled,R.airplay&&l(I.types.html5,rt.type)),y(rt.elements.container,I.classes.stopped,I.autoplay),y(rt.elements.container,I.classes.isIos,rt.browser.isIos),y(rt.elements.container,I.classes.isTouch,rt.browser.isTouch),"video"===rt.type)){var e=d("div");e.setAttribute("class",I.classes.videoWrapper),i(rt.elements.media,e),rt.elements.wrapper=e}l(I.types.embed,rt.type)&&be()}function be(){var t,n=d("div"),s=rt.type+"-"+Math.floor(1e4*Math.random());switch(rt.type){case"youtube":t=A(rt.embedId);break;case"vimeo":t=_(rt.embedId);break;default:t=rt.embedId}for(var l=D('[id^="'+rt.type+'-"]'),i=l.length-1;i>=0;i--)r(l[i]);if(y(rt.elements.media,I.classes.videoWrapper,!0),y(rt.elements.media,I.classes.embedWrapper,!0),"youtube"===rt.type)rt.elements.media.appendChild(n),n.setAttribute("id",s),j.object(e.YT)?ge(t,n):(a(I.urls.youtube.api),e.onYouTubeReadyCallbacks=e.onYouTubeReadyCallbacks||[],e.onYouTubeReadyCallbacks.push(function(){ge(t,n)}),e.onYouTubeIframeAPIReady=function(){e.onYouTubeReadyCallbacks.forEach(function(e){e()})});else if("vimeo"===rt.type)if(rt.supported.full?rt.elements.media.appendChild(n):n=rt.elements.media,n.setAttribute("id",s),j.object(e.Vimeo))he(t,n);else{a(I.urls.vimeo.api);var o=e.setInterval(function(){j.object(e.Vimeo)&&(e.clearInterval(o),he(t,n))},50)}else if("soundcloud"===rt.type){var c=d("iframe");c.loaded=!1,w(c,"load",function(){c.loaded=!0}),u(c,{src:"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/"+t,id:s}),n.appendChild(c),rt.elements.media.appendChild(n),e.SC||a(I.urls.soundcloud.api);var p=e.setInterval(function(){e.SC&&c.loaded&&(e.clearInterval(p),ke.call(c))},50)}}function ve(){rt.supported.full&&(st(),lt()),pe(W("iframe"))}function ge(t,n){rt.embed=new e.YT.Player(n.id,{videoId:t,playerVars:{autoplay:I.autoplay?1:0,controls:rt.supported.full?0:1,rel:0,showinfo:0,iv_load_policy:3,cc_load_policy:I.captions.defaultActive?1:0,cc_lang_pref:"en",wmode:"transparent",modestbranding:1,disablekb:1,origin:"*"},events:{onError:function(e){V(rt.elements.container,"error",!0,{code:e.data,embed:e.target})},onPlaybackQualityChange:function(e){var t=e.target,n=t.getPlaybackQuality();console.warn(n)},onReady:function(t){var n=t.target;rt.elements.media.play=function(){n.playVideo(),rt.elements.media.paused=!1},rt.elements.media.pause=function(){n.pauseVideo(),rt.elements.media.paused=!0},rt.elements.media.stop=function(){n.stopVideo(),rt.elements.media.paused=!0},rt.elements.media.duration=n.getDuration(),rt.elements.media.paused=!0,rt.elements.media.currentTime=0,rt.elements.media.muted=n.isMuted();var a=n.getPlaybackRate(),s=n.getAvailablePlaybackRates();console.warn(a,s),I.title=n.getVideoData().title,rt.supported.full&&rt.elements.media.querySelector("iframe").setAttribute("tabindex","-1"),ve(),V(rt.elements.media,"timeupdate"),V(rt.elements.media,"durationchange"),e.clearInterval(ot.buffering),ot.buffering=e.setInterval(function(){rt.elements.media.buffered=n.getVideoLoadedFraction(),(null===rt.elements.media.lastBuffered||rt.elements.media.lastBuffered<rt.elements.media.buffered)&&V(rt.elements.media,"progress"),rt.elements.media.lastBuffered=rt.elements.media.buffered,1===rt.elements.media.buffered&&(e.clearInterval(ot.buffering),V(rt.elements.media,"canplaythrough"))},200)},onStateChange:function(t){var n=t.target;switch(e.clearInterval(ot.playing),t.data){case 0:rt.elements.media.paused=!0,V(rt.elements.media,"ended");break;case 1:rt.elements.media.paused=!1,rt.elements.media.seeking&&V(rt.elements.media,"seeked"),rt.elements.media.seeking=!1,V(rt.elements.media,"play"),V(rt.elements.media,"playing"),ot.playing=e.setInterval(function(){rt.elements.media.currentTime=n.getCurrentTime(),V(rt.elements.media,"timeupdate")},100),rt.elements.media.duration!==n.getDuration()&&(rt.elements.media.duration=n.getDuration(),V(rt.elements.media,"durationchange"));var a=n.getAvailableQualityLevels(),s=n.getPlaybackQuality();Z(a,s);break;case 2:rt.elements.media.paused=!0,V(rt.elements.media,"pause")}V(rt.elements.container,"statechange",!1,{code:t.data})}}})}function he(t,n){rt.embed=new e.Vimeo.Player(n,{id:parseInt(t),loop:I.loop.active,autoplay:I.autoplay,byline:!1,portrait:!1,title:!1}),rt.elements.media.play=function(){rt.embed.play(),rt.elements.media.paused=!1},rt.elements.media.pause=function(){rt.embed.pause(),rt.elements.media.paused=!0},rt.elements.media.stop=function(){rt.embed.stop(),rt.elements.media.paused=!0},rt.elements.media.paused=!0,rt.elements.media.currentTime=0,ve(),rt.embed.getCurrentTime().then(function(e){rt.elements.media.currentTime=e,V(rt.elements.media,"timeupdate")}),rt.embed.getDuration().then(function(e){rt.elements.media.duration=e,V(rt.elements.media,"durationchange")}),rt.embed.on("loaded",function(){j.htmlElement(rt.embed.element)&&rt.supported.full&&rt.embed.element.setAttribute("tabindex","-1")}),rt.embed.on("play",function(){rt.elements.media.paused=!1,V(rt.elements.media,"play"),V(rt.elements.media,"playing")}),rt.embed.on("pause",function(){rt.elements.media.paused=!0,V(rt.elements.media,"pause")}),rt.embed.on("timeupdate",function(e){rt.elements.media.seeking=!1,rt.elements.media.currentTime=e.seconds,V(rt.elements.media,"timeupdate")}),rt.embed.on("progress",function(e){rt.elements.media.buffered=e.percent,V(rt.elements.media,"progress"),1===parseInt(e.percent)&&V(rt.elements.media,"canplaythrough")}),rt.embed.on("seeked",function(){rt.elements.media.seeking=!1,V(rt.elements.media,"seeked"),V(rt.elements.media,"play")}),rt.embed.on("ended",function(){rt.elements.media.paused=!0,V(rt.elements.media,"ended")})}function ke(){rt.embed=e.SC.Widget(this),rt.embed.bind(e.SC.Widget.Events.READY,function(){rt.elements.media.play=function(){rt.embed.play(),rt.elements.media.paused=!1},rt.elements.media.pause=function(){rt.embed.pause(),rt.elements.media.paused=!0},rt.elements.media.stop=function(){rt.embed.seekTo(0),rt.embed.pause(),rt.elements.media.paused=!0},rt.elements.media.paused=!0,rt.elements.media.currentTime=0,rt.embed.getDuration(function(e){rt.elements.media.duration=e/1e3,ve()}),rt.embed.getPosition(function(e){rt.elements.media.currentTime=e,V(rt.elements.media,"timeupdate")}),rt.embed.bind(e.SC.Widget.Events.PLAY,function(){rt.elements.media.paused=!1,V(rt.elements.media,"play"),V(rt.elements.media,"playing")}),rt.embed.bind(e.SC.Widget.Events.PAUSE,function(){rt.elements.media.paused=!0,V(rt.elements.media,"pause")}),rt.embed.bind(e.SC.Widget.Events.PLAY_PROGRESS,function(e){rt.elements.media.seeking=!1,rt.elements.media.currentTime=e.currentPosition/1e3,V(rt.elements.media,"timeupdate")}),rt.embed.bind(e.SC.Widget.Events.LOAD_PROGRESS,function(e){rt.elements.media.buffered=e.loadProgress,V(rt.elements.media,"progress"),1===parseInt(e.loadProgress)&&V(rt.elements.media,"canplaythrough")}),rt.embed.bind(e.SC.Widget.Events.FINISH,function(){rt.elements.media.paused=!0,V(rt.elements.media,"ended")})})}function we(){"play"in rt.elements.media&&rt.elements.media.play()}function Ce(){"pause"in rt.elements.media&&rt.elements.media.pause()}function xe(e){return j.boolean(e)||(e=rt.elements.media.paused),e?we():Ce(),e}function Te(e){l(["start","end","all","none","toggle"],e)||(e="toggle");var n=Number(rt.elements.media.currentTime);switch(e){case"start":I.loop.end&&I.loop.end<=n&&(I.loop.end=null),I.loop.start=n,I.loop.indicator.start=rt.elements.display.played.value;break;case"end":if(I.loop.start>=n)return;I.loop.end=n,I.loop.indicator.end=rt.elements.display.played.value;break;case"all":I.loop.start=0,I.loop.end=rt.elements.media.duration-2,I.loop.indicator.start=0,I.loop.indicator.end=100;break;case"toggle":I.loop.active?(I.loop.start=0,I.loop.end=null):(I.loop.start=0,I.loop.end=rt.elements.media.duration-2);break;default:I.loop.start=0,I.loop.end=null}I.loop.active=j.number(I.loop.start)&&j.number(I.loop.end);var a=(Ye(I.loop.start,W('[data-plyr-loop="start"]')),null);j.number(I.loop.end)&&(a=Ye(I.loop.end,t.querySelector('[data-loop__value="loopout"]'))),I.loop.active}function Se(e){if(j.undefined(e)&&(e=rt.storage.speed||I.defaultSpeed),!j.array(I.speeds))return void dt("Invalid speeds format");if(!j.number(e)){var t=I.speeds.indexOf(I.currentSpeed);if(t!==-1){var n=t+1;n>=I.speeds.length&&(n=0),e=I.speeds[n]}else e=I.defaultSpeed}I.currentSpeed=e,rt.elements.media.playbackRate=e,fe({speed:e})}function Ee(){return I.currentSpeed.toFixed(1).toString().replace(".0","")+"&times;"}function Ae(e){j.number(e)||(e=I.seekTime),Ie(rt.elements.media.currentTime-e)}function _e(e){j.number(e)||(e=I.seekTime),Ie(rt.elements.media.currentTime+e)}function Ie(e){var t=0,n=rt.elements.media.paused,a=Pe();j.number(e)?t=e:j.event(e)&&l(["input","change"],e.type)&&(t=e.target.value/e.target.max*a),t<0?t=0:t>a&&(t=a),Je(t);try{rt.elements.media.currentTime=t.toFixed(4)}catch(e){}if(l(I.types.embed,rt.type)){switch(rt.type){case"youtube":rt.embed.seekTo(t);break;case"vimeo":rt.embed.setCurrentTime(t.toFixed(0));break;case"soundcloud":rt.embed.seekTo(1e3*t)}n&&Ce(),V(rt.elements.media,"timeupdate"),rt.elements.media.seeking=!0,V(rt.elements.media,"seeking")}ct("Seeking to "+rt.elements.media.currentTime+" seconds"),ie(t)}function Pe(){var e=parseInt(I.duration),t=0;return null===rt.elements.media.duration||isNaN(rt.elements.media.duration)||(t=rt.elements.media.duration),isNaN(e)?t:e}function Ne(){y(rt.elements.container,I.classes.playing,!rt.elements.media.paused),y(rt.elements.container,I.classes.stopped,rt.elements.media.paused),Qe(rt.elements.media.paused)}function Fe(){L={x:e.pageXOffset||0,y:e.pageYOffset||0}}function qe(){e.scrollTo(L.x,L.y)}function Le(e){var n=R.fullscreen;if(n){if(!e||e.type!==M.eventType)return M.isFullScreen(rt.elements.container)?M.cancelFullScreen():(Fe(),M.requestFullScreen(rt.elements.container)),void(rt.fullscreen.active=M.isFullScreen(rt.elements.container));rt.fullscreen.active=M.isFullScreen(rt.elements.container)}else rt.fullscreen.active=!rt.fullscreen.active,t.body.style.overflow=rt.fullscreen.active?"hidden":"";y(rt.elements.container,I.classes.fullscreen.active,rt.fullscreen.active),H(rt.fullscreen.active),rt.elements.buttons&&rt.elements.buttons.fullscreen&&T(rt.elements.buttons.fullscreen,rt.fullscreen.active),V(rt.elements.container,rt.fullscreen.active?"enterfullscreen":"exitfullscreen",!0),!rt.fullscreen.active&&n&&qe()}function Oe(n){var a=rt.elements.settings.menu.parentNode,s=n.target,l=t.getElementById(s.getAttribute("aria-controls")),i="false"===s.getAttribute("aria-expanded");if(j.htmlElement(l)){var o,u,c,d="tabpanel"===l.getAttribute("role");if(d){var p=a.querySelector('[role="tabpanel"][aria-hidden="false"]');c=p.parentNode,[].forEach.call(a.querySelectorAll('[aria-controls="'+p.getAttribute("id")+'"]'),function(e){e.setAttribute("aria-expanded",!1)}),c.style.width=p.scrollWidth+"px",c.style.height=p.scrollHeight+"px",p.setAttribute("aria-hidden",!0),p.setAttribute("tabindex",-1);var m=l.cloneNode(!0);m.style.position="absolute",m.style.opacity=0,m.setAttribute("aria-hidden",!1),c.appendChild(m),o=m.scrollWidth,u=m.scrollHeight,r(m)}l.setAttribute("aria-hidden",!i),s.setAttribute("aria-expanded",i),l.setAttribute("tabindex",0),d&&(c.style.width=o+"px",c.style.height=u+"px",e.setTimeout(function(){c.style.width="",c.style.height=""},300))}}function je(e){if(j.boolean(e)||(e=!rt.elements.media.muted),T(rt.elements.buttons.mute,e),rt.elements.media.muted=e,0===rt.elements.media.volume&&Me(I.volume),l(I.types.embed,rt.type)){switch(rt.type){case"youtube":rt.embed[rt.elements.media.muted?"mute":"unMute"]();break;case"vimeo":case"soundcloud":rt.embed.setVolume(rt.elements.media.muted?0:parseFloat(I.volume/10))}V(rt.elements.media,"volumechange")}}function Me(e){var t=10,n=0;if(j.event(e)&&(e=e.target.value),j.undefined(e)&&(e=rt.storage.volume),(null===e||isNaN(e))&&(e=I.volume),e>t&&(e=t),e<n&&(e=n),rt.elements.media.volume=parseFloat(e/t),rt.elements.display.volume&&(rt.elements.display.volume.value=e),l(I.types.embed,rt.type)){switch(rt.type){case"youtube":rt.embed.setVolume(100*rt.elements.media.volume);break;case"vimeo":case"soundcloud":rt.embed.setVolume(rt.elements.media.volume)}V(rt.elements.media,"volumechange")}0===e?rt.elements.media.muted=!0:rt.elements.media.muted&&e>0&&je()}function Re(e){var t=rt.elements.media.muted?0:10*rt.elements.media.volume;j.number(e)||(e=1),Me(t+e)}function Ve(e){var t=rt.elements.media.muted?0:10*rt.elements.media.volume;j.number(e)||(e=1),Me(t-e)}function De(){var e=rt.elements.media.muted?0:10*rt.elements.media.volume;rt.supported.full&&(rt.elements.inputs.volume&&(rt.elements.inputs.volume.value=e),rt.elements.display.volume&&(rt.elements.display.volume.value=e)),fe({volume:e}),y(rt.elements.container,I.classes.muted,0===e),rt.supported.full&&rt.elements.buttons.mute&&T(rt.elements.buttons.mute,0===e)}function We(e){var t="waiting"===e.type;clearTimeout(ot.loading),ot.loading=setTimeout(function(){y(rt.elements.container,I.classes.loading,t),Qe(t)},t?250:0)}function He(e){if(rt.supported.full){var t=rt.elements.display.played,n=0,a=Pe();if(e)switch(e.type){case"timeupdate":case"seeking":if(rt.elements.controls.pressed)return;n=S(rt.elements.media.currentTime,a),"timeupdate"===e.type&&rt.elements.inputs.seek&&(rt.elements.inputs.seek.value=n);break;case"playing":case"progress":t=rt.elements.display.buffer,n=function(){var e=rt.elements.media.buffered;return e&&e.length?S(e.end(0),a):j.number(e)?100*e:0}()}j.number(I.loop.start)&&j.number(I.loop.end)&&rt.elements.media.currentTime>=I.loop.end&&Ie(I.loop.start),Be(t,n)}}function Be(e,t){if(rt.supported.full){if(j.undefined(t)&&(t=0),j.undefined(e)){if(!j.htmlElement(rt.elements.display.buffer))return;e=rt.elements.display.buffer}if(j.htmlElement(e)){e.value=t;var n=e.getElementsByTagName("span")[0];j.htmlElement(n)&&(n.childNodes[0].nodeValue=t)}}}function Ye(e,t){if(t){isNaN(e)&&(e=0);
+var n=parseInt(e%60),a=parseInt(e/60%60),s=parseInt(e/60/60%60),l=parseInt(Pe()/60/60%60)>0;n=("0"+n).slice(-2),a=("0"+a).slice(-2);var i=(l?s+":":"")+a+":"+n;return t.textContent=i,i}}function Xe(){if(rt.supported.full){var e=Pe()||0;!rt.elements.display.duration&&I.displayDuration&&rt.elements.media.paused&&Ye(e,rt.elements.display.currentTime),rt.elements.display.duration&&Ye(e,rt.elements.display.duration),Ke()}}function Ue(e){Ye(rt.elements.media.currentTime,rt.elements.display.currentTime),e&&"timeupdate"===e.type&&rt.elements.media.seeking||He(e)}function Je(e){j.number(e)||(e=0);var t=Pe(),n=S(e,t);rt.elements.progress&&rt.elements.display.played&&(rt.elements.display.played.value=n),rt.elements.buttons&&rt.elements.inputs.seek&&(rt.elements.inputs.seek.value=n)}function Ke(e){var t=Pe();if(I.tooltips.seek&&j.htmlElement(rt.elements.inputs.seek)&&j.htmlElement(rt.elements.display.seekTooltip)&&0!==t){var n=rt.elements.inputs.seek.getBoundingClientRect(),a=0,s=I.classes.tooltip+"--visible";if(j.event(e))a=100/n.width*(e.pageX-n.left);else{if(!b(rt.elements.display.seekTooltip,s))return;a=rt.elements.display.seekTooltip.style.left.replace("%","")}a<0?a=0:a>100&&(a=100),Ye(t/100*a,rt.elements.display.seekTooltip),rt.elements.display.seekTooltip.style.left=a+"%",j.event(e)&&l(["mouseenter","mouseleave"],e.type)&&y(rt.elements.display.seekTooltip,s,"mouseenter"===e.type)}}function Qe(t){if(I.hideControls&&"audio"!==rt.type){var n=0,a=!1,s=t,i=b(rt.elements.container,I.classes.loading);if(j.boolean(t)||(t&&t.type?(a="enterfullscreen"===t.type,s=l(["mousemove","touchstart","mouseenter","focus"],t.type),l(["mousemove","touchmove"],t.type)&&(n=2e3),"focus"===t.type&&(n=3e3)):s=b(rt.elements.container,I.classes.hideControls)),e.clearTimeout(ot.hover),s||rt.elements.media.paused||i){if(y(rt.elements.container,I.classes.hideControls,!1),rt.elements.media.paused||i)return;rt.browser.isTouch&&(n=3e3)}s&&rt.elements.media.paused||(ot.hover=e.setTimeout(function(){(!rt.elements.controls.pressed&&!rt.elements.controls.hover||a)&&y(rt.elements.container,I.classes.hideControls,!0)},n))}}function ze(e){if(!j.undefined(e))return void $e(e);var t;switch(rt.type){case"youtube":t=rt.embed.getVideoUrl();break;case"vimeo":rt.embed.getVideoUrl.then(function(e){t=e});break;case"soundcloud":rt.embed.getCurrentSound(function(e){t=e.permalink_url});break;default:t=rt.elements.media.currentSrc}return t||""}function $e(e){function t(){if(rt.embed=null,r(rt.elements.media),"video"===rt.type&&rt.elements.wrapper&&r(rt.elements.wrapper),rt.elements.container&&rt.elements.container.removeAttribute("class"),"type"in e&&(rt.type=e.type,"video"===rt.type)){var t=e.sources[0];"type"in t&&l(I.types.embed,t.type)&&(rt.type=t.type)}switch(rt.supported=N(rt.type),rt.type){case"video":rt.elements.media=d("video");break;case"audio":rt.elements.media=d("audio");break;case"youtube":case"vimeo":case"soundcloud":rt.elements.media=d("div"),rt.embedId=e.sources[0].src}o(rt.elements.container,rt.elements.media),j.boolean(e.autoplay)&&(I.autoplay=e.autoplay),l(I.types.html5,rt.type)&&(I.crossorigin&&rt.elements.media.setAttribute("crossorigin",""),I.autoplay&&rt.elements.media.setAttribute("autoplay",""),"poster"in e&&rt.elements.media.setAttribute("poster",e.poster),I.loop.active&&rt.elements.media.setAttribute("loop","")),y(rt.elements.container,I.classes.fullscreen.active,rt.fullscreen.active),y(rt.elements.container,I.classes.captions.active,rt.captions.enabled),ce(),l(I.types.html5,rt.type)&&B("source",e.sources),ye(),l(I.types.html5,rt.type)&&("tracks"in e&&B("track",e.tracks),rt.elements.media.load()),(l(I.types.html5,rt.type)||l(I.types.embed,rt.type)&&!rt.supported.full)&&(st(),lt()),I.title=e.title,pe()}return j.object(e)&&"sources"in e&&e.sources.length?(y(rt.elements.container,I.classes.ready,!1),Ce(),Je(),Be(),tt(),void nt(t,!1)):void dt("Invalid source format")}function Ge(e){"video"===rt.type&&rt.elements.media.setAttribute("poster",e)}function Ze(){function n(){var e=xe(),t=rt.elements.buttons[e?"play":"pause"],n=rt.elements.buttons[e?"pause":"play"];if(n){var a=b(t,I.classes.tabFocus);setTimeout(function(){n.focus(),a&&(y(t,I.classes.tabFocus,!1),y(n,I.classes.tabFocus,!0))},100)}}function a(e){return e.keyCode?e.keyCode:e.which}function s(e){for(var t in rt.elements.buttons){var n=rt.elements.buttons[t];if(j.nodeList(n))for(var a=0;a<n.length;a++)y(n[a],I.classes.tabFocus,n[a]===e);else y(n,I.classes.tabFocus,n===e)}}function i(e){function t(){var e=rt.elements.media.duration;j.number(e)&&Ie(e/10*(s-48))}var s=a(e),i="keydown"===e.type,r=i&&s===o;if(j.number(s))if(i){var u=[48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67,73,76,79],c=[38,40];if(l(c,s)){var d=g();if(j.htmlElement(d)&&"radio"===g().type)return}switch(l(u,s)&&(e.preventDefault(),e.stopPropagation()),s){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:r||t();break;case 32:case 75:r||n();break;case 38:Re();break;case 40:Ve();break;case 77:r||je();break;case 39:_e();break;case 37:Ae();break;case 70:Le();break;case 67:r||oe();break;case 73:Te("start");break;case 76:Te();break;case 79:Te("end")}!R.fullscreen&&rt.fullscreen.active&&27===s&&Le(),o=s}else o=null}var r=rt.browser.isIE?"change":"input";if(I.keyboardShortcuts.focused){var o=null;I.keyboardShortcuts.global&&w(e,"keydown keyup",function(e){var t=a(e),n=g(),s=[48,49,50,51,52,53,54,56,57,75,77,70,67,73,76,79],r=q().length;1!==r||!l(s,t)||j.htmlElement(n)&&v(n,I.selectors.editable)||i(e)}),w(rt.elements.container,"keydown keyup",i)}w(e,"keyup",function(e){var t=a(e),n=g();9===t&&s(n)}),w(t.body,"click",function(){y(W("."+I.classes.tabFocus),I.classes.tabFocus,!1)});for(var u in rt.elements.buttons){var c=rt.elements.buttons[u];w(c,"blur",function(){y(c,"tab-focus",!1)})}var d=function(e,t,n){j.function(t)&&t.call(this,e),j.function(n)&&n.call(this,e)};h(rt.elements.buttons.play,"click",I.listeners.play,n),h(rt.elements.buttons.playLarge,"click",I.listeners.play,n),h(rt.elements.buttons.pause,"click",I.listeners.pause,n),h(rt.elements.buttons.restart,"click",I.listeners.restart,Ie),h(rt.elements.buttons.rewind,"click",I.listeners.rewind,Ae),h(rt.elements.buttons.forward,"click",I.listeners.forward,_e),h(rt.elements.buttons.mute,"click",I.listeners.mute,je),h(rt.elements.buttons.captions,"click",I.listeners.captions,oe),h(rt.elements.buttons.fullscreen,"click",I.listeners.fullscreen,Le),h(rt.elements.buttons.pip,"click",I.listeners.pip,function(e){R.pip&&rt.elements.media.webkitSetPresentationMode("picture-in-picture"===rt.elements.media.webkitPresentationMode?"inline":"picture-in-picture")}),h(rt.elements.buttons.airplay,"click",I.listeners.airplay,function(e){R.airplay&&rt.elements.media.webkitShowPlaybackTargetPicker()}),w(rt.elements.settings.menu,"click",Oe),w(t.body,"click",function(e){var t=rt.elements.settings.menu,n=t.querySelector("form");"true"===n.getAttribute("aria-hidden")||t.contains(e.target)||n.setAttribute("aria-hidden",!0)}),w(rt.elements.settings.menu,"click",function(e){v(e.target,I.selectors.buttons.speed)?d.call(this,e,I.listeners.speed,function(){console.warn("Set speed")}):v(e.target,I.selectors.buttons.quality)?d.call(this,e,I.listeners.quality,function(){console.warn("Set quality")}):v(e.target,I.selectors.buttons.loop)?d.call(this,e,I.listeners.loop,function(){var t=e.target.getAttribute("data-loop__value")||e.target.getAttribute("data-loop__type");l(["start","end","all","none"],t)&&Te(t)}):v(e.target,I.selectors.buttons.language)&&d.call(this,e,I.listeners.language,function(e){var t=e.target.attributes.getNamedItem("data-index").value;se(t)})}),h(rt.elements.inputs.seek,r,I.listeners.seek,Ie),h(rt.elements.inputs.volume,r,I.listeners.volume,Me),w(rt.elements.progress,"mouseenter mouseleave mousemove",Ke),I.hideControls&&(w(rt.elements.container,"mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen",Qe),w(rt.elements.controls,"mouseenter mouseleave",function(e){rt.elements.controls.hover="mouseenter"===e.type}),w(rt.elements.controls,"mousedown mouseup touchstart touchend touchcancel",function(e){rt.elements.controls.pressed=l(["mousedown","touchstart"],e.type)}),w(rt.elements.controls,"focus blur",Qe,!0)),h(rt.elements.inputs.volume,"wheel",I.listeners.volume,function(e){var t=e.webkitDirectionInvertedFromDevice,n=.2,a=0;(e.deltaY<0||e.deltaX>0)&&(t?(Ve(n),a=-1):(Re(n),a=1)),(e.deltaY>0||e.deltaX<0)&&(t?(Re(n),a=1):(Ve(n),a=-1)),(1===a&&rt.elements.media.volume<1||a===-1&&rt.elements.media.volume>0)&&e.preventDefault()}),R.fullscreen&&w(t,M.eventType,Le)}function et(){if(w(rt.elements.media,"timeupdate seeking",Ue),w(rt.elements.media,"timeupdate",ie),w(rt.elements.media,"durationchange loadedmetadata",Xe),w(rt.elements.media,"ended",function(){"video"===rt.type&&I.showPosterOnEnd&&("video"===rt.type&&le(),Ie(),rt.elements.media.load())}),w(rt.elements.media,"progress playing",He),w(rt.elements.media,"volumechange",De),w(rt.elements.media,"play pause ended",Ne),w(rt.elements.media,"waiting canplay seeked",We),I.clickToPlay&&"audio"!==rt.type){var e=W("."+I.classes.videoWrapper);if(!e)return;e.style.cursor="pointer",w(e,"click",function(){I.hideControls&&rt.browser.isTouch&&!rt.elements.media.paused||(rt.elements.media.paused?we():rt.elements.media.ended?(Ie(),we()):Ce())})}I.disableContextMenu&&w(rt.elements.media,"contextmenu",function(e){e.preventDefault()}),w(rt.elements.media,I.events.concat(["keyup","keydown"]).join(" "),function(e){V(rt.elements.container,e.type,!0)})}function tt(){if(l(I.types.html5,rt.type)){for(var e=rt.elements.media.querySelectorAll("source"),t=0;t<e.length;t++)r(e[t]);rt.elements.media.setAttribute("src","https://cdn.selz.com/plyr/blank.mp4"),rt.elements.media.load(),ct("Cancelled network requests")}}function nt(n,a){function s(){j.boolean(a)||(a=!0),j.function(n)&&n.call(ut),a&&(rt.init=!1,rt.elements.container.parentNode.replaceChild(ut,rt.elements.container),t.body.style.overflow="",V(ut,"destroyed",!0))}if(!rt.init)return null;switch(rt.type){case"youtube":e.clearInterval(ot.buffering),e.clearInterval(ot.playing),rt.embed.destroy(),s();break;case"vimeo":rt.embed.unload().then(s),e.setTimeout(s,200);break;case"video":case"audio":de(!0),s()}}function at(){if(rt.init)return null;if(rt.browser=n(),j.htmlElement(rt.elements.media)){me();var e=k.tagName.toLowerCase();"div"===e?(rt.type=k.getAttribute("data-type"),rt.embedId=k.getAttribute("data-video-id"),k.removeAttribute("data-type"),k.removeAttribute("data-video-id")):(rt.type=e,I.crossorigin=null!==k.getAttribute("crossorigin"),I.autoplay=I.autoplay||null!==k.getAttribute("autoplay"),I.loop=I.loop||null!==k.getAttribute("loop")),rt.supported=N(rt.type),rt.supported.basic&&(rt.elements.container=i(k,d("div")),rt.elements.container.setAttribute("tabindex",0),ce(),ct(""+rt.browser.name+" "+rt.browser.version),ye(),(l(I.types.html5,rt.type)||l(I.types.embed,rt.type)&&!rt.supported.full)&&(st(),lt(),pe()),rt.init=!0)}}function st(){return rt.supported.full?(j.htmlElement(W(I.selectors.controls.wrapper))||(ue(),Ze()),et(),de(),te(),ae(),se(),Me(),De(),Se(),Te(),Ue(),void Ne()):(dt("Basic support only",rt.type),r(W(I.selectors.controls.wrapper)),r(W(I.selectors.buttons.play)),void de(!0))}function lt(){e.setTimeout(function(){V(rt.elements.media,"ready")},0),y(rt.elements.media,O.classes.setup,!0),y(rt.elements.container,I.classes.ready,!0),rt.elements.media.plyr=it,I.autoplay&&we()}var it,rt=this,ot={};rt.fullscreen={active:!1},rt.elements={buttons:{},display:{},progress:{},inputs:{},settings:{menu:null,panes:{},tabs:{}},media:k},rt.captions={enabled:!1,textTracks:!1,captions:[]};var ut=k.cloneNode(!0),ct=function(){F("log",arguments)},dt=function(){F("warn",arguments)};return ct("Config",I),ct("Support",R),it={getOriginal:function(){return ut},getContainer:function(){return rt.elements.container},getEmbed:function(){return rt.embed},getMedia:function(){return rt.elements.media},getType:function(){return rt.type},getDuration:Pe,getCurrentTime:function(){return rt.elements.media.currentTime},getVolume:function(){return rt.elements.media.volume},isMuted:function(){return rt.elements.media.muted},isReady:function(){return b(rt.elements.container,I.classes.ready)},isLoading:function(){return b(rt.elements.container,I.classes.loading)},isPaused:function(){return rt.elements.media.paused},isLooping:function(){return I.loop.active},on:function(e,t){return w(rt.elements.container,e,t),this},play:we,pause:Ce,loop:Te,stop:function(){Ce(),Ie()},restart:Ie,rewind:Ae,forward:_e,seek:Ie,source:ze,poster:Ge,setVolume:Me,setSpeed:Se,togglePlay:xe,toggleMute:je,toggleCaptions:oe,toggleFullscreen:Le,toggleControls:Qe,setCaptionIndex:se,isFullscreen:function(){return rt.fullscreen.active||!1},support:function(e){return R.mime(rt,e)},destroy:nt},at(),rt.init?it:null}function P(e,n){var a=new XMLHttpRequest;if(!j.string(n)||!j.htmlElement(t.querySelector("#"+n))){var s=d("div");s.setAttribute("hidden",""),j.string(n)&&s.setAttribute("id",n),t.body.insertBefore(s,t.body.childNodes[0]),"withCredentials"in a&&(a.open("GET",e,!0),a.onload=function(){s.innerHTML=a.responseText},a.send())}}function N(e){var t,a,s=n(),l=s.isIE&&s.version<=9,i=s.isIos,r=/iPhone|iPod/i.test(navigator.userAgent),o=!!d("audio").canPlayType,u=!!d("video").canPlayType;switch(e){case"video":t=u,a=t&&!l&&!r;break;case"audio":t=o,a=t&&!l;break;case"vimeo":case"youtube":case"soundcloud":t=!0,a=!l&&!i;break;default:t=o&&u,a=t&&!l}return{basic:t,full:a}}function F(e,n){function a(e,t){b(t,O.classes.hook)||s.push({target:e,media:t})}var s=[],l=[],i=[O.selectors.html5,O.selectors.embed].join(",");if(j.string(e)?e=t.querySelectorAll(e):j.htmlElement(e)?e=[e]:j.nodeList(e)||j.array(e)||j.string(e)||(j.undefined(n)&&j.object(e)&&(n=e),e=t.querySelectorAll(i)),j.nodeList(e)&&(e=Array.prototype.slice.call(e)),!N().basic||!e.length)return!1;for(var r=0;r<e.length;r++){var o=e[r],u=o.querySelectorAll(i);if(u.length)for(var c=0;c<u.length;c++)a(o,u[c]);else v(o,i)&&a(o,o)}return s.forEach(function(e){var t=e.target,a=e.media,s=!1;a===t&&(s=!0);var i={};try{i=JSON.parse(t.getAttribute("data-plyr"))}catch(e){}var r=E({},O,n,i);if(!r.enabled)return null;var o=new I(a,r);if(j.object(o)){if(r.debug){var u=r.events.concat(["setup","statechange","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled"]);w(o.getContainer(),u.join(" "),function(e){console.log([r.logPrefix,"event:",e.type].join(" "),e.detail.plyr)})}x(o.getContainer(),"setup",!0,{plyr:o}),l.push(o)}}),l}function q(e){if(j.string(e)?e=t.querySelector(e):j.undefined(e)&&(e=t.body),j.htmlElement(e)){var n=e.querySelectorAll("."+O.classes.setup),a=[];return Array.prototype.slice.call(n).forEach(function(e){j.object(e.plyr)&&a.push(e.plyr)}),a}return[]}var L={x:0,y:0},O={enabled:!0,debug:!1,autoplay:!1,loop:{active:!1,start:0,end:null,indicator:{start:0,end:0}},seekTime:10,volume:10,defaultSpeed:1,currentSpeed:1,speeds:[.5,1,1.5,2],duration:null,displayDuration:!0,loadSprite:!0,iconPrefix:"plyr",iconUrl:"https://cdn.plyr.io/2.0.10/plyr.svg",clickToPlay:!0,hideControls:!0,showPosterOnEnd:!1,disableContextMenu:!0,quality:{options:!1},keyboardShortcuts:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},tracks:[],selectors:{html5:"video, audio",embed:"[data-type]",editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',forward:'[data-plyr="fast-forward"]',mute:'[data-plyr="mute"]',captions:'[data-plyr="captions"]',fullscreen:'[data-plyr="fullscreen"]',pip:'[data-plyr="pip"]',airplay:'[data-plyr="airplay"]',settings:'[data-plyr="settings"]',speed:'[data-plyr="speed"]',loop:'[data-plyr="loop"]',language:'[data-plyr="language"]',quality:'[data-plyr="quality"]'},inputs:{seek:'[data-plyr="seek"]',volume:'[data-plyr="volume"]'},display:{currentTime:".plyr__time--current",duration:".plyr__time--duration",buffer:".plyr__progress--buffer",played:".plyr__progress--played",loop:".plyr__progress--loop",volume:".plyr__volume--display"},progress:".plyr__progress",captions:".plyr__captions",menu:{quality:".js-plyr__menu__list--quality"}},classes:{setup:"plyr--setup",ready:"plyr--ready",videoWrapper:"plyr__video-wrapper",embedWrapper:"plyr__video-embed",control:"plyr__control",type:"plyr--{0}",stopped:"plyr--stopped",playing:"plyr--playing",muted:"plyr--muted",loading:"plyr--loading",hover:"plyr--hover",tooltip:"plyr__tooltip",hidden:"plyr__sr-only",hideControls:"plyr--hide-controls",isIos:"plyr--is-ios",isTouch:"plyr--is-touch",menu:{value:"plyr__menu__value",badge:"plyr__badge"},captions:{enabled:"plyr--captions-enabled",active:"plyr--captions-active"},fullscreen:{enabled:"plyr--fullscreen-enabled",active:"plyr--fullscreen-active"},pip:{enabled:"plyr--pip-enabled",active:"plyr--pip-active"},airplay:{enabled:"plyr--airplay-enabled",active:"plyr--airplay-active"},tabFocus:"tab-focus"},captions:{defaultActive:!1,selectedIndex:0},fullscreen:{enabled:!0,fallback:!0,allowAudio:!1},storage:{enabled:!0,key:"plyr"},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","pip","airplay","fullscreen"],i18n:{restart:"Restart",rewind:"Rewind {seektime} secs",play:"Play",pause:"Pause",forward:"Forward {seektime} secs",seek:"Seek",played:"Played",buffered:"Buffered",currentTime:"Current time",duration:"Duration",volume:"Volume",toggleMute:"Toggle Mute",toggleCaptions:"Toggle Captions",toggleFullscreen:"Toggle Fullscreen",frameTitle:"Player for {title}",captions:"Captions",settings:"Settings",speed:"Speed",quality:"Quality",loop:"Loop",start:"Start",end:"End",all:"All",reset:"Reset"},types:{embed:["youtube","vimeo","soundcloud"],html5:["video","audio"]},urls:{vimeo:{api:"https://player.vimeo.com/api/player.js"},youtube:{api:"https://www.youtube.com/iframe_api"},soundcloud:{api:"https://w.soundcloud.com/player/api.js"}},listeners:{seek:null,play:null,pause:null,restart:null,rewind:null,forward:null,mute:null,volume:null,captions:null,fullscreen:null,pip:null,airplay:null,speed:null,quality:null,loop:null,language:null},events:["ready","ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied"],logPrefix:""},j={object:function(e){return null!==e&&"object"==typeof e&&e.constructor===Object},array:function(e){return null!==e&&"object"==typeof e&&e.constructor===Array},number:function(e){return null!==e&&("number"==typeof e&&!isNaN(e-0)||"object"==typeof e&&e.constructor===Number)},string:function(e){return null!==e&&("string"==typeof e||"object"==typeof e&&e.constructor===String)},boolean:function(e){return null!==e&&"boolean"==typeof e},nodeList:function(e){return null!==e&&e instanceof NodeList},htmlElement:function(e){return null!==e&&e instanceof HTMLElement},function:function(e){return null!==e&&"function"==typeof e},event:function(e){return null!==e&&e instanceof Event},undefined:function(e){return null!==e&&"undefined"==typeof e},empty:function(e){return null===e||this.undefined(e)||(this.string(e)||this.array(e)||this.nodeList(e))&&0===e.length||this.object(e)&&0===Object.keys(e).length}},M=function(){var e=function(){var e=!1;return j.function(t.cancelFullScreen)?e="":["webkit","o","moz","ms","khtml"].some(function(n){return j.function(t[n+"CancelFullScreen"])?(e=n,!0):j.function(t.msExitFullscreen)&&t.msFullscreenEnabled?(e="ms",!0):void 0}),e}();return{prefix:e,eventType:"ms"===e?"MSFullscreenChange":e+"fullscreenchange",isFullScreen:function(n){if(!R.fullscreen)return!1;switch(j.undefined(n)&&(n=t.body),this.prefix){case"":return t.fullscreenElement===n;case"moz":return t.mozFullScreenElement===n;default:return t[e+"FullscreenElement"]===n}},requestFullScreen:function(n){return!!R.fullscreen&&(j.htmlElement(n)||(n=t.body),console.log(e),""===e?n.requestFullScreen():n[e+("ms"===e?"RequestFullscreen":"RequestFullScreen")]())},cancelFullScreen:function(){return!!R.fullscreen&&(""===e?t.cancelFullScreen():t[e+("ms"===e?"ExitFullscreen":"CancelFullScreen")]())},element:function(){return R.fullscreen?""===e?t.fullscreenElement:t[e+"FullscreenElement"]:null}}}(),R={fullscreen:M.prefix!==!1,storage:function(){if(!("localStorage"in e))return!1;try{e.localStorage.setItem("___test","OK");var t=e.localStorage.getItem("___test");return e.localStorage.removeItem("___test"),"OK"===t}catch(e){return!1}return!1}(),pip:function(){return j.function(d("video").webkitSetPresentationMode)}(),airplay:function(){return j.function(e.WebKitPlaybackTargetAvailabilityEvent)}(),mime:function(e,t){var n=e.media;try{if(!j.function(n.canPlayType))return!1;if("video"===e.type)switch(t){case"video/webm":return n.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/,"");case"video/mp4":return n.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/,"");case"video/ogg":return n.canPlayType('video/ogg; codecs="theora"').replace(/no/,"")}else if("audio"===e.type)switch(t){case"audio/mpeg":return n.canPlayType("audio/mpeg;").replace(/no/,"");case"audio/ogg":return n.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/,"");case"audio/wav":return n.canPlayType('audio/wav; codecs="1"').replace(/no/,"")}}catch(e){return!1}return!1}};return{setup:F,supported:N,loadSprite:P,get:q}}),function(){function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),n}"function"!=typeof window.CustomEvent&&(e.prototype=window.Event.prototype,window.CustomEvent=e)}(); \ No newline at end of file
diff --git a/dist/plyr.svg b/dist/plyr.svg
index aab6e3e3..3db87c38 100644
--- a/dist/plyr.svg
+++ b/dist/plyr.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg"><symbol id="plyr-captions-off" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd" fill-opacity=".5"/></symbol><symbol id="plyr-captions-on" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd"/></symbol><symbol id="plyr-enter-fullscreen" viewBox="0 0 18 18"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z"/></symbol><symbol id="plyr-exit-fullscreen" viewBox="0 0 18 18"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z"/></symbol><symbol id="plyr-fast-forward" viewBox="0 0 18 18"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z"/></symbol><symbol id="plyr-muted" viewBox="0 0 18 18"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol><symbol id="plyr-pause" viewBox="0 0 18 18"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z"/></symbol><symbol id="plyr-play" viewBox="0 0 18 18"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"/></symbol><symbol id="plyr-restart" viewBox="0 0 18 18"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z"/></symbol><symbol id="plyr-rewind" viewBox="0 0 18 18"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z"/></symbol><symbol id="plyr-volume" viewBox="0 0 18 18"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z"/><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol></svg> \ No newline at end of file
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg"><symbol id="plyr-airplay" viewBox="0 0 18 18"><path d="M16 1H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2H3V3h12v8h-2v2h3a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/><path d="M4 17h10l-5-6z"/></symbol><symbol id="plyr-captions-off" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd" fill-opacity=".5"/></symbol><symbol id="plyr-captions-on" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd"/></symbol><symbol id="plyr-enter-fullscreen" viewBox="0 0 18 18"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z"/></symbol><symbol id="plyr-exit-fullscreen" viewBox="0 0 18 18"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z"/></symbol><symbol id="plyr-fast-forward" viewBox="0 0 18 18"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z"/></symbol><symbol id="plyr-muted" viewBox="0 0 18 18"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol><symbol id="plyr-pause" viewBox="0 0 18 18"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z"/></symbol><symbol id="plyr-pip" viewBox="0 0 18 18"><path d="M13.293 3.293L7.022 9.564l1.414 1.414 6.271-6.271L17 7V1h-6z"/><path d="M13 15H3V5h5V3H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6h-2v5z"/></symbol><symbol id="plyr-play" viewBox="0 0 18 18"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"/></symbol><symbol id="plyr-restart" viewBox="0 0 18 18"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z"/></symbol><symbol id="plyr-rewind" viewBox="0 0 18 18"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z"/></symbol><symbol id="plyr-settings" viewBox="0 0 18 18"><path d="M16.135 7.784a2 2 0 0 1-1.23-2.969c.322-.536.225-.998-.094-1.316l-.31-.31c-.318-.318-.78-.415-1.316-.094a2 2 0 0 1-2.969-1.23C10.065 1.258 9.669 1 9.219 1h-.438c-.45 0-.845.258-.997.865a2 2 0 0 1-2.969 1.23c-.536-.322-.999-.225-1.317.093l-.31.31c-.318.318-.415.781-.093 1.317a2 2 0 0 1-1.23 2.969C1.26 7.935 1 8.33 1 8.781v.438c0 .45.258.845.865.997a2 2 0 0 1 1.23 2.969c-.322.536-.225.998.094 1.316l.31.31c.319.319.782.415 1.316.094a2 2 0 0 1 2.969 1.23c.151.607.547.865.997.865h.438c.45 0 .845-.258.997-.865a2 2 0 0 1 2.969-1.23c.535.321.997.225 1.316-.094l.31-.31c.318-.318.415-.781.094-1.316a2 2 0 0 1 1.23-2.969c.607-.151.865-.547.865-.997v-.438c0-.451-.26-.846-.865-.997zM9 12a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol id="plyr-volume" viewBox="0 0 18 18"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z"/><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol></svg> \ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
index 923483e5..7b891b3b 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,9 +1,9 @@
// ==========================================================================
// Gulp build script
// ==========================================================================
-/*global require, __dirname,Buffer*/
-/*jshint -W079 */
-
+/* global require, __dirname,Buffer */
+/* jshint -W079 */
+/* beautify ignore:start */
var fs = require("fs"),
path = require("path"),
gulp = require("gulp"),
@@ -77,20 +77,20 @@ function loadJSON(path) {
// Create a file from a string
// http://stackoverflow.com/questions/23230569/how-do-you-create-a-file-from-a-string-in-gulp
function createFile(filename, string) {
- var src = require('stream').Readable({
- objectMode: true
+ var src = require('stream').Readable({
+ objectMode: true
});
src._read = function () {
- this.push(new gutil.File({
- cwd: "",
- base: "",
- path: filename,
+ this.push(new gutil.File({
+ cwd: "",
+ base: "",
+ path: filename,
contents: new Buffer(string),
// stats also required for some functions
// https://nodejs.org/api/fs.html#fs_class_fs_stats
stat: {
size: string.length
- }
+ }
}));
this.push(null);
}
@@ -296,7 +296,7 @@ gulp.task("demo", function () {
});
// Open the demo site to check it's sweet
-gulp.task("symlinks", function () {
+/*gulp.task("symlinks", function () {
console.log("Updating symlinks...");
return gulp.src(paths.upload)
@@ -316,7 +316,7 @@ gulp.task("symlinks", function () {
callback(null, chunk);
}));
-});
+});*/
// Open the demo site to check it's sweet
gulp.task("open", function () {
@@ -333,5 +333,7 @@ gulp.task("open", function () {
// Do everything
gulp.task("publish", function () {
- run(tasks.js, tasks.less, tasks.sprite, "cdn", "demo", "symlinks");
+ run(tasks.js, tasks.less, tasks.sprite, "cdn", "demo");
});
+
+/* beautify ignore:end */
diff --git a/notes.md b/notes.md
new file mode 100644
index 00000000..42c2d3d1
--- /dev/null
+++ b/notes.md
@@ -0,0 +1,34 @@
+### Todo
+
+#### To build
+- Get list of subtitles/captions available (HTML5)
+- Add preferred quality option into config
+- Update quality options on YouTube play (can't get up front?!)
+- Update speed options on YouTube load
+
+- Get quality options for HTML5 somehow (multi source?)
+- Build templating for controls somehow
+- Finish and test PiP (need Sierra VM)
+- Finish and test AirPlay (need Sierra VM)
+
+- Download button - grab first <source> or src attribute (or maybe use currentSrc?) for HTML5 and links for embedded players
+
+# Notes
+- No quality HTML5 support (yet)
+- No Vimeo quality support
+- No Vimeo or YouTube caption support
+- No PiP or AirPlay for Vimeo/YouTube
+
+#### Bugs
+- Fix audio setup bug when calling .setup() again
+- Fix events on unsupported devices (iOS)
+- Investigate iOS inline playback
+- Look at Vimeo's "background" option
+
+#### Breaking changes
+- Custom controls HTML removed (temporarily, will return) - perhaps can re-instate but no options UI
+- Selectors changes (new `input` and `display` object) - DOCUMENT
+
+## Added
+- Seek i8n label
+- Loop related i8n labels \ No newline at end of file
diff --git a/package.json b/package.json
index 3fe071de..72ed32ee 100644
--- a/package.json
+++ b/package.json
@@ -7,21 +7,21 @@
"dependencies": {},
"devDependencies": {
"gulp": "^3.9.1",
- "gulp-autoprefixer": "^3.1.0",
- "gulp-clean-css": "^2.0.6",
+ "gulp-autoprefixer": "^3.1.1",
+ "gulp-clean-css": "^2.0.12",
"gulp-concat": "^2.3.3",
"gulp-less": "^3.0.5",
"gulp-open": "^2.0.0",
"gulp-rename": "^1.2.0",
"gulp-replace": "^0.5.3",
"gulp-s3": "^0.3.0",
- "gulp-sass": "^2.3.1",
+ "gulp-sass": "^2.3.2",
"gulp-size": "^2.1.0",
"gulp-svgmin": "^1.2.2",
- "gulp-svgstore": "^5.0.5",
- "gulp-uglify": "^1.5.3",
+ "gulp-svgstore": "^6.0.0",
+ "gulp-uglify": "^2.0.0",
"gulp-util": "^3.0.7",
- "run-sequence": "^1.1.5",
+ "run-sequence": "^1.2.2",
"through2": "^2.0.1"
},
"keywords": [
diff --git a/readme.md b/readme.md
index 6110b57a..fade70e2 100644
--- a/readme.md
+++ b/readme.md
@@ -34,7 +34,7 @@ Check out the [changelog](changelog.md) to see what's new with Plyr.
## Features currently being developed
- Playback speed selection
-- Quality selection
+- Quality selection
- Caption language selection
- AirPlay
- Picture in Picture (MacOS Sierra + Safari)
@@ -126,7 +126,7 @@ Note: `data-video-id` value can now be the ID or URL for the video. This attribu
```
Note: `data-video-id` value can now be the ID or URL for the video. This attribute name will change in a future release to reflect this change.
-### 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 [initialising](#initialising).
```html
@@ -159,7 +159,7 @@ The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https:
## Advanced
### LESS & SASS/SCSS
-You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should already) as all declerations use the W3C definitions - e.g. `appearance: none;` will be prefixed to `-webkit-appearance: none;` by autoprefixer.
+You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should already) as all declerations use the W3C definitions - e.g. `appearance: none;` will be prefixed to `-webkit-appearance: none;` by autoprefixer.
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS you write. Check out the JavaScript source for more on this.
@@ -167,7 +167,7 @@ The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__
The icons used in the Plyr controls are loaded in an SVG sprite. The sprite is automatically loaded from our CDN by default. If you already have an icon build system in place, you can include the source plyr icons (see `/src/sprite` for source icons).
#### Using the `iconUrl` option
-You can however specify your own `iconUrl` option and Plyr will determine if the url is absolute and requires loading by AJAX/CORS due to current browser limitations or if it's a relative path, just use the path directly.
+You can however specify your own `iconUrl` option and Plyr will determine if the url is absolute and requires loading by AJAX/CORS due to current browser limitations or if it's a relative path, just use the path directly.
If you're using the `<base>` tag on your site, you may need to use something like this:
[svgfixer.js](https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2)
@@ -213,7 +213,7 @@ Passing a [string selector](https://developer.mozilla.org/en-US/docs/Web/API/Doc
plyr.setup('.js-player', options);
```
-The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-type]` (for embeds) element itself or a container element.
+The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-type]` (for embeds) element itself or a container element.
Passing just the options object:
```javascript
@@ -223,7 +223,7 @@ plyr.setup(options);
`setup()` will return an array of *instances* that can be used with the [API](#api) methods. See the [API](#api) section for more info.
#### RangeTouch
-Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby touching the track to set the value doesn't work and sliding the thumb can be tricky. To combat this, I've created [RangeTouch](https://rangetouch.com) which I'd recommend including in your solution. It's a tiny script with a nice benefit for users on touch devices.
+Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby touching the track to set the value doesn't work and sliding the thumb can be tricky. To combat this, I've created [RangeTouch](https://rangetouch.com) which I'd recommend including in your solution. It's a tiny script with a nice benefit for users on touch devices.
#### Options
Options must be passed as an object to the `setup()` method as above or as JSON in `data-plyr` attribute on each of your target elements:
@@ -369,28 +369,16 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
<td>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 <code>controls</code> option).</td>
</tr>
<tr>
- <td><code>selectors</code></td>
- <td>Object</td>
- <td>&mdash;</td>
- <td>See <code>plyr.js</code> in <code>/src</code> for more info. You probably don't need to change any of these.</td>
- </tr>
- <tr>
<td><code>listeners</code></td>
<td>Object</td>
<td>&mdash;</td>
<td>Allows early binding of event listeners to the controls. See <code>controls</code> above for list of controls and see <code>plyr.js</code> in <code>/src</code> for more info.</td>
</tr>
<tr>
- <td><code>classes</code></td>
- <td>Object</td>
- <td>&mdash;</td>
- <td>Similar to above, these are the classes added to the player when state changes occur.</td>
- </tr>
- <tr>
<td><code>captions</code></td>
<td>Object</td>
<td>&mdash;</td>
- <td>One property <code>defaultActive</code> which toggles if captions should be on by default. The default value is <code>false</code>.</td>
+ <td>Two properties: <code>defaultActive</code> which toggles if captions should be on by default. The default value is <code>false</code>. The <code>selectedIndex</code> property sets the default starting index for the caption tracks.</td>
</tr>
<tr>
<td><code>fullscreen</code></td>
@@ -404,6 +392,18 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
<td>&mdash;</td>
<td>Two properties; <code>enabled</code> which toggles if local storage should be enabled (if the browser supports it). The default value is `true`. This enables storing user settings, currently it only stores volume but more will be added later. The second property <code>key</code> is the key used for the local storage. The default is <code>plyr_volume</code> until more settings are stored.</td>
</tr>
+ <tr>
+ <td><code>speeds</code></td>
+ <td>Array</td>
+ <td>[1.0, 1.5, 2.0, 0.5]</td>
+ <td>Playback speed list.</td>
+ </tr>
+ <tr>
+ <td><code>loops</code></td>
+ <td>Array</td>
+ <td>[Loop All, Loop in, Loop out, No Loop]</td>
+ <td>Playback loop list.</td>
+ </tr>
</tbody>
</table>
@@ -456,7 +456,7 @@ This will return an array of all instances that were setup. Another way is to us
var players = plyr.get('.js-player');
```
-If no argument is passed, it will find all instances in the current document. This will return an array of all instances that were found in the given selector.
+If no argument is passed, it will find all instances in the current document. This will return an array of all instances that were found in the given selector.
A final option is to access the instance through the event handlers:
@@ -596,6 +596,11 @@ Here's a list of the methods supported:
<td>Toggles whether captions are enabled.</td>
</tr>
<tr>
+ <td><code>setCaptionIndex()</code></td>
+ <td>Number</td>
+ <td>Set the active track to the provided number. Index starts with 0.</td>
+ </tr>
+ <tr>
<td><code>toggleFullscreen()</code></td>
<td>Event</td>
<td>Toggles fullscreen. This can only be initiated by a user gesture due to browser security, i.e. a user event such as click.</td>
@@ -663,7 +668,12 @@ player.source({
srclang:'en',
src: '/path/to/captions.vtt',
default: true
- }]
+ }],
+ loopKeyEvents: {
+ toggleLoop: 76,
+ loopin: 73,
+ loopout: 79
+ }
});
```
@@ -908,7 +918,7 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
Plyr references a custom version of the Vimeo Froogaloop API as Vimeo have neglected to maintain the library and there were bugs with their version. You don't need to worry about including your own versions of the Vimeo or YouTube JavaScript APIs.
-The embed third party API's can be accessed through the `getEmbed()` API method.
+The embed third party API's can be accessed through the `getEmbed()` API method.
More info on the respective API's here:
@@ -918,7 +928,7 @@ More info on the respective API's here:
*Please note*: not all API methods may work 100%. Your mileage may vary. It's better to use the universal plyr API where possible.
## Shortcuts
-By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to `true` and there's only one player in the document then the shortcuts will work when any element has focus, apart from an element that requires input.
+By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to `true` and there's only one player in the document then the shortcuts will work when any element has focus, apart from an element that requires input.
<table class="table" width="100%">
<thead>
@@ -933,7 +943,7 @@ By default, a player will bind the following keyboard shortcuts when it has focu
<td><code>0</code> to <code>9</code></td>
<td>✔</td>
<td>Seek from 0 to 90% respectively</td>
- </tr>
+ </tr>
<tr>
<td><code>space</code></td>
<td></td>
@@ -979,10 +989,25 @@ By default, a player will bind the following keyboard shortcuts when it has focu
<td>✔</td>
<td>Toggle captions</td>
</tr>
+ <tr>
+ <td><code>l</code></td>
+ <td></td>
+ <td>Toggle Loop All/No Loop</td>
+ </tr>
+ <tr>
+ <td><code>i</code></td>
+ <td></td>
+ <td>Set the start marker of the loop</td>
+ </tr>
+ <tr>
+ <td><code>o</code></td>
+ <td></td>
+ <td>Set the end marker of the loop</td>
+ </tr>
</tbody>
</table>
-## Streaming
+## Streaming
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:
- Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
@@ -990,7 +1015,7 @@ Because Plyr is an extension of the standard HTML5 video and audio elements, thi
- Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN)
## Fullscreen
-Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen).
+Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen).
## Browser support
@@ -1082,7 +1107,7 @@ Also these links helped created Plyr:
## Thanks
[![Fastly](https://www.fastly.com/sites/all/themes/custom/fastly2016/logo.png)](https://www.fastly.com/)
-Thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
+Thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
## Copyright and License
[The MIT license](license.md).
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 7d57a10c..92c5175d 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,22 +1,24 @@
// ==========================================================================
// Plyr
-// plyr.js v2.0.12
+// plyr.js v2.0.10
// https://github.com/selz/plyr
// License: The MIT License (MIT)
// ==========================================================================
// Credits: http://paypal.github.io/accessible-html5-video-player/
// ==========================================================================
-;(function(root, factory) {
+(function(root, factory) {
'use strict';
- /*global define,module*/
+ /* global define,module */
if (typeof module === 'object' && typeof module.exports === 'object') {
// Node, CommonJS-like
module.exports = factory(root, document);
} else if (typeof define === 'function' && define.amd) {
// AMD
- define([], function () { return factory(root, document); });
+ define([], function() {
+ return factory(root, document);
+ });
} else {
// Browser globals (root is window)
root.plyr = factory(root, document);
@@ -25,176 +27,299 @@
'use strict';
// Globals
- var fullscreen,
- scroll = { x: 0, y: 0 },
+ var scroll = {
+ x: 0,
+ y: 0
+ };
// Default config
- defaults = {
- enabled: true,
- debug: false,
- autoplay: false,
- loop: false,
- seekTime: 10,
- volume: 10,
- volumeMin: 0,
- volumeMax: 10,
- volumeStep: 1,
- duration: null,
- displayDuration: true,
- loadSprite: true,
- iconPrefix: 'plyr',
- iconUrl: 'https://cdn.plyr.io/2.0.12/plyr.svg',
- blankUrl: 'https://cdn.selz.com/plyr/blank.mp4',
- clickToPlay: true,
- hideControls: true,
- showPosterOnEnd: false,
- disableContextMenu: true,
- keyboardShorcuts: {
- focused: true,
- global: false
+ var defaults = {
+ enabled: true,
+ debug: false,
+ autoplay: false,
+ loop: {
+ active: false,
+ start: 0,
+ end: null,
+ indicator: {
+ start: 0,
+ end: 0
+ }
+ },
+ seekTime: 10,
+ volume: 10,
+ defaultSpeed: 1.0,
+ currentSpeed: 1,
+ speeds: [0.5, 1.0, 1.5, 2.0],
+ duration: null,
+ displayDuration: true,
+ loadSprite: true,
+ iconPrefix: 'plyr',
+ iconUrl: 'https://cdn.plyr.io/2.0.10/plyr.svg',
+ clickToPlay: true,
+ hideControls: true,
+ showPosterOnEnd: false,
+ disableContextMenu: true,
+ quality: {
+ options: false
+ },
+ keyboardShortcuts: {
+ focused: true,
+ global: false
},
tooltips: {
- controls: false,
- seek: true
+ controls: false,
+ seek: true
},
+ tracks: [],
selectors: {
- html5: 'video, audio',
- embed: '[data-type]',
- editable: 'input, textarea, select, [contenteditable]',
- container: '.plyr',
+ html5: 'video, audio',
+ embed: '[data-type]',
+ editable: 'input, textarea, select, [contenteditable]',
+ container: '.plyr',
controls: {
- container: null,
- wrapper: '.plyr__controls'
+ container: null,
+ wrapper: '.plyr__controls'
},
- labels: '[data-plyr]',
+ labels: '[data-plyr]',
buttons: {
- seek: '[data-plyr="seek"]',
- play: '[data-plyr="play"]',
- pause: '[data-plyr="pause"]',
- restart: '[data-plyr="restart"]',
- rewind: '[data-plyr="rewind"]',
- forward: '[data-plyr="fast-forward"]',
- mute: '[data-plyr="mute"]',
- captions: '[data-plyr="captions"]',
- fullscreen: '[data-plyr="fullscreen"]'
+ play: '[data-plyr="play"]',
+ pause: '[data-plyr="pause"]',
+ restart: '[data-plyr="restart"]',
+ rewind: '[data-plyr="rewind"]',
+ forward: '[data-plyr="fast-forward"]',
+ mute: '[data-plyr="mute"]',
+ captions: '[data-plyr="captions"]',
+ fullscreen: '[data-plyr="fullscreen"]',
+ pip: '[data-plyr="pip"]',
+ airplay: '[data-plyr="airplay"]',
+ settings: '[data-plyr="settings"]',
+ speed: '[data-plyr="speed"]',
+ loop: '[data-plyr="loop"]',
+ language: '[data-plyr="language"]',
+ quality: '[data-plyr="quality"]'
},
- volume: {
- input: '[data-plyr="volume"]',
- display: '.plyr__volume--display'
+ inputs: {
+ seek: '[data-plyr="seek"]',
+ volume: '[data-plyr="volume"]'
},
- progress: {
- container: '.plyr__progress',
- buffer: '.plyr__progress--buffer',
- played: '.plyr__progress--played'
+ display: {
+ currentTime: '.plyr__time--current',
+ duration: '.plyr__time--duration',
+ buffer: '.plyr__progress--buffer',
+ played: '.plyr__progress--played',
+ loop: '.plyr__progress--loop',
+ volume: '.plyr__volume--display',
},
- captions: '.plyr__captions',
- currentTime: '.plyr__time--current',
- duration: '.plyr__time--duration'
+ progress: '.plyr__progress',
+ captions: '.plyr__captions',
+ menu: {
+ quality: '.js-plyr__menu__list--quality'
+ }
},
classes: {
- setup: 'plyr--setup',
- ready: 'plyr--ready',
- videoWrapper: 'plyr__video-wrapper',
- embedWrapper: 'plyr__video-embed',
- type: 'plyr--{0}',
- stopped: 'plyr--stopped',
- playing: 'plyr--playing',
- muted: 'plyr--muted',
- loading: 'plyr--loading',
- hover: 'plyr--hover',
- tooltip: 'plyr__tooltip',
- hidden: 'plyr__sr-only',
- hideControls: 'plyr--hide-controls',
- isIos: 'plyr--is-ios',
- isTouch: 'plyr--is-touch',
+ setup: 'plyr--setup',
+ ready: 'plyr--ready',
+ videoWrapper: 'plyr__video-wrapper',
+ embedWrapper: 'plyr__video-embed',
+ control: 'plyr__control',
+ type: 'plyr--{0}',
+ stopped: 'plyr--stopped',
+ playing: 'plyr--playing',
+ muted: 'plyr--muted',
+ loading: 'plyr--loading',
+ hover: 'plyr--hover',
+ tooltip: 'plyr__tooltip',
+ hidden: 'plyr__sr-only',
+ hideControls: 'plyr--hide-controls',
+ isIos: 'plyr--is-ios',
+ isTouch: 'plyr--is-touch',
+ menu: {
+ value: 'plyr__menu__value',
+ badge: 'plyr__badge'
+ },
captions: {
- enabled: 'plyr--captions-enabled',
- active: 'plyr--captions-active'
+ enabled: 'plyr--captions-enabled',
+ active: 'plyr--captions-active'
},
fullscreen: {
- enabled: 'plyr--fullscreen-enabled',
- active: 'plyr--fullscreen-active'
+ enabled: 'plyr--fullscreen-enabled',
+ active: 'plyr--fullscreen-active'
+ },
+ pip: {
+ enabled: 'plyr--pip-enabled',
+ active: 'plyr--pip-active'
+ },
+ airplay: {
+ enabled: 'plyr--airplay-enabled',
+ active: 'plyr--airplay-active'
},
- tabFocus: 'tab-focus'
+ tabFocus: 'tab-focus'
},
captions: {
- defaultActive: false
+ defaultActive: false,
+ selectedIndex: 0
},
fullscreen: {
- enabled: true,
- fallback: true,
- allowAudio: false
+ enabled: true,
+ fallback: true,
+ allowAudio: false
},
storage: {
- enabled: true,
- key: 'plyr'
+ enabled: true,
+ key: 'plyr'
},
- controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'],
+ controls: [
+ 'play-large',
+ 'play',
+ 'progress',
+ 'current-time',
+ 'mute',
+ 'volume',
+ 'captions',
+ 'settings',
+ 'pip',
+ 'airplay',
+ 'fullscreen'
+ ],
i18n: {
- restart: 'Restart',
- rewind: 'Rewind {seektime} secs',
- play: 'Play',
- pause: 'Pause',
- forward: 'Forward {seektime} secs',
- played: 'played',
- buffered: 'buffered',
- currentTime: 'Current time',
- duration: 'Duration',
- volume: 'Volume',
- toggleMute: 'Toggle Mute',
- toggleCaptions: 'Toggle Captions',
- toggleFullscreen: 'Toggle Fullscreen',
- frameTitle: 'Player for {title}'
+ restart: 'Restart',
+ rewind: 'Rewind {seektime} secs',
+ play: 'Play',
+ pause: 'Pause',
+ forward: 'Forward {seektime} secs',
+ seek: 'Seek',
+ played: 'Played',
+ buffered: 'Buffered',
+ currentTime: 'Current time',
+ duration: 'Duration',
+ volume: 'Volume',
+ toggleMute: 'Toggle Mute',
+ toggleCaptions: 'Toggle Captions',
+ toggleFullscreen: 'Toggle Fullscreen',
+ frameTitle: 'Player for {title}',
+ captions: 'Captions',
+ settings: 'Settings',
+ speed: 'Speed',
+ quality: 'Quality',
+ loop: 'Loop',
+ start: 'Start',
+ end: 'End',
+ all: 'All',
+ reset: 'Reset',
},
types: {
- embed: ['youtube', 'vimeo', 'soundcloud'],
- html5: ['video', 'audio']
+ embed: ['youtube', 'vimeo', 'soundcloud'],
+ html5: ['video', 'audio']
},
// URLs
urls: {
vimeo: {
- api: 'https://player.vimeo.com/api/player.js',
+ api: 'https://player.vimeo.com/api/player.js',
},
youtube: {
- api: 'https://www.youtube.com/iframe_api'
+ api: 'https://www.youtube.com/iframe_api'
},
soundcloud: {
- api: 'https://w.soundcloud.com/player/api.js'
+ api: 'https://w.soundcloud.com/player/api.js'
}
},
// Custom control listeners
listeners: {
- seek: null,
- play: null,
- pause: null,
- restart: null,
- rewind: null,
- forward: null,
- mute: null,
- volume: null,
- captions: null,
- fullscreen: null
+ seek: null,
+ play: null,
+ pause: null,
+ restart: null,
+ rewind: null,
+ forward: null,
+ mute: null,
+ volume: null,
+ captions: null,
+ fullscreen: null,
+ pip: null,
+ airplay: null,
+ speed: null,
+ quality: null,
+ loop: null,
+ language: null
},
// Events to watch on HTML5 media elements
- events: ['ready', 'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied'],
+ events: [
+ 'ready',
+ 'ended',
+ 'progress',
+ 'stalled',
+ 'playing',
+ 'waiting',
+ 'canplay',
+ 'canplaythrough',
+ 'loadstart',
+ 'loadeddata',
+ 'loadedmetadata',
+ 'timeupdate',
+ 'volumechange',
+ 'play',
+ 'pause',
+ 'error',
+ 'seeking',
+ 'seeked',
+ 'emptied'
+ ],
// Logging
- logPrefix: '[Plyr]'
+ logPrefix: ''
+ };
+
+ // Check variable types
+ var is = {
+ object: function(input) {
+ return input !== null && typeof(input) === 'object' && input.constructor === Object;
+ },
+ array: function(input) {
+ return input !== null && typeof(input) === 'object' && input.constructor === Array;
+ },
+ number: function(input) {
+ return input !== null && (typeof(input) === 'number' && !isNaN(input - 0) || (typeof input === 'object' && input.constructor === Number));
+ },
+ string: function(input) {
+ return input !== null && (typeof input === 'string' || (typeof input === 'object' && input.constructor === String));
+ },
+ boolean: function(input) {
+ return input !== null && typeof input === 'boolean';
+ },
+ nodeList: function(input) {
+ return input !== null && input instanceof NodeList;
+ },
+ htmlElement: function(input) {
+ return input !== null && input instanceof HTMLElement;
+ },
+ function: function(input) {
+ return input !== null && typeof input === 'function';
+ },
+ event: function(input) {
+ return input !== null && input instanceof Event;
+ },
+ undefined: function(input) {
+ return input !== null && typeof input === 'undefined';
+ },
+ empty: function(input) {
+ return input === null || this.undefined(input) || ((this.string(input) || this.array(input) || this.nodeList(input)) && input.length === 0) || (this.object(input) && Object.keys(input).length === 0);
+ }
};
// Credits: http://paypal.github.io/accessible-html5-video-player/
// Unfortunately, due to mixed support, UA sniffing is required
- function _browserSniff() {
- var ua = navigator.userAgent,
- name = navigator.appName,
- fullVersion = '' + parseFloat(navigator.appVersion),
- majorVersion = parseInt(navigator.appVersion, 10),
- nameOffset,
- verOffset,
- ix,
- isIE = false,
- isFirefox = false,
- isChrome = false,
- isSafari = false;
+ function getBrowser() {
+ var ua = navigator.userAgent;
+ var name = navigator.appName;
+ var fullVersion = '' + parseFloat(navigator.appVersion);
+ var majorVersion = parseInt(navigator.appVersion, 10);
+ var nameOffset;
+ var verOffset;
+ var ix;
+ var isIE = false;
+ var isFirefox = false;
+ var isChrome = false;
+ var isSafari = false;
if ((navigator.appVersion.indexOf('Windows NT') !== -1) && (navigator.appVersion.indexOf('rv:11') !== -1)) {
// MSIE 11
@@ -216,6 +341,7 @@
isSafari = true;
name = 'Safari';
fullVersion = ua.substring(verOffset + 7);
+
if ((verOffset = ua.indexOf('Version')) !== -1) {
fullVersion = ua.substring(verOffset + 8);
}
@@ -226,7 +352,7 @@
fullVersion = ua.substring(verOffset + 8);
} else if ((nameOffset = ua.lastIndexOf(' ') + 1) < (verOffset = ua.lastIndexOf('/'))) {
// In most other browsers, 'name/version' is at the end of userAgent
- name = ua.substring(nameOffset,verOffset);
+ name = ua.substring(nameOffset, verOffset);
fullVersion = ua.substring(verOffset + 1);
if (name.toLowerCase() === name.toUpperCase()) {
@@ -251,68 +377,51 @@
// Return data
return {
- name: name,
- version: majorVersion,
- isIE: isIE,
- isFirefox: isFirefox,
- isChrome: isChrome,
- isSafari: isSafari,
- isIos: /(iPad|iPhone|iPod)/g.test(navigator.platform),
- isIphone: /(iPhone|iPod)/g.test(navigator.userAgent),
- isTouch: 'ontouchstart' in document.documentElement
+ name: name,
+ version: majorVersion,
+ isIE: isIE,
+ isFirefox: isFirefox,
+ isChrome: isChrome,
+ isSafari: isSafari,
+ isIos: /(iPad|iPhone|iPod)/g.test(navigator.platform),
+ isTouch: 'ontouchstart' in document.documentElement
};
}
- // Check for mime type support against a player instance
- // Credits: http://diveintohtml5.info/everything.html
- // Related: http://www.leanbackplyr.com/test/h5mt.html
- function _supportMime(plyr, mimeType) {
- var media = plyr.media;
-
- if (plyr.type === 'video') {
- // Check type
- switch (mimeType) {
- case 'video/webm': return !!(media.canPlayType && media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''));
- case 'video/mp4': return !!(media.canPlayType && media.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''));
- case 'video/ogg': return !!(media.canPlayType && media.canPlayType('video/ogg; codecs="theora"').replace(/no/, ''));
- }
- } else if (plyr.type === 'audio') {
- // Check type
- switch (mimeType) {
- case 'audio/mpeg': return !!(media.canPlayType && media.canPlayType('audio/mpeg;').replace(/no/, ''));
- case 'audio/ogg': return !!(media.canPlayType && media.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
- case 'audio/wav': return !!(media.canPlayType && media.canPlayType('audio/wav; codecs="1"').replace(/no/, ''));
- }
- }
-
- // If we got this far, we're stuffed
- return false;
- }
-
// Inject a script
- function _injectScript(source) {
+ function injectScript(source) {
if (document.querySelectorAll('script[src="' + source + '"]').length) {
return;
}
var tag = document.createElement('script');
tag.src = source;
+
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
+ // Determine if we're in an iframe
+ function inFrame() {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+ }
+
// Element exists in an array
- function _inArray(haystack, needle) {
+ function inArray(haystack, needle) {
return Array.prototype.indexOf && (haystack.indexOf(needle) !== -1);
}
// Replace all
- function _replaceAll(string, find, replace) {
+ function replaceAll(string, find, replace) {
return string.replace(new RegExp(find.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), replace);
}
// Wrap an element
- function _wrap(elements, wrapper) {
+ function wrap(elements, wrapper) {
// Convert `elements` to an array, if necessary.
if (!elements.length) {
elements = [elements];
@@ -321,11 +430,11 @@
// Loops backwards to prevent having to clone the wrapper on the
// first element (see `child` below).
for (var i = elements.length - 1; i >= 0; i--) {
- var child = (i > 0) ? wrapper.cloneNode(true) : wrapper;
+ var child = (i > 0) ? wrapper.cloneNode(true) : wrapper;
var element = elements[i];
// Cache the current parent and sibling.
- var parent = element.parentNode;
+ var parent = element.parentNode;
var sibling = element.nextSibling;
// Wrap the element (is automatically removed from its current
@@ -345,23 +454,8 @@
}
}
- // Unwrap an element
- // http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/
- /*function _unwrap(wrapper) {
- // Get the element's parent node
- var parent = wrapper.parentNode;
-
- // Move all children out of the element
- while (wrapper.firstChild) {
- parent.insertBefore(wrapper.firstChild, wrapper);
- }
-
- // Remove the empty element
- parent.removeChild(wrapper);
- }*/
-
// Remove an element
- function _remove(element) {
+ function remove(element) {
if (!element) {
return;
}
@@ -369,36 +463,119 @@
}
// Prepend child
- function _prependChild(parent, element) {
+ function prependChild(parent, element) {
parent.insertBefore(element, parent.firstChild);
}
// Set attributes
- function _setAttributes(element, attributes) {
+ function setAttributes(element, attributes) {
for (var key in attributes) {
- element.setAttribute(key, (_is.boolean(attributes[key]) && attributes[key]) ? '' : attributes[key]);
+ element.setAttribute(key, attributes[key]);
+ }
+ }
+
+ // Get an attribute object from a string selector
+ function getAttributesFromSelector(selector, existingAttributes) {
+ // For example:
+ // '.test' to { class: 'test' }
+ // '#test' to { id: 'test' }
+ // '[data-test="test"]' to { 'data-test': 'test' }
+
+ if (!is.string(selector) || is.empty(selector)) {
+ return {};
}
+
+ var attributes = {};
+
+ selector.split(',').forEach(function(selector) {
+ // Remove whitespace
+ selector = selector.trim();
+
+ // Get the first character
+ var start = selector.charAt(0);
+
+ switch (start) {
+ case '.':
+ // Classname selector
+ var className = selector.replace('.', '');
+
+ // Add to existing classname
+ if (is.object(existingAttributes) && is.string(existingAttributes.class)) {
+ existingAttributes.class += ' ' + className;
+ }
+
+ attributes.class = className;
+ break;
+
+ case '#':
+ // ID selector
+ attributes.id = selector.replace('#', '');
+ break;
+
+ case '[':
+ // Strip the []
+ selector = selector.replace(/[\[\]]/g, '');
+
+ // Get the parts if
+ var parts = selector.split('=');
+ var key = parts[0];
+
+ // Get the value if provided
+ var value = parts.length > 1 ? parts[1].replace(/[\"\']/g, '') : '';
+
+ // Attribute selector
+ attributes[key] = value;
+
+ break;
+ }
+ });
+
+ return attributes;
}
- // Insert a HTML element
- function _insertElement(type, parent, attributes) {
+ // Create a DocumentFragment
+ function createElement(type, attributes, text) {
// Create a new <element>
var element = document.createElement(type);
// Set all passed attributes
- _setAttributes(element, attributes);
+ if (is.object(attributes)) {
+ setAttributes(element, attributes);
+ }
+
+ // Add text node
+ if (is.string(text)) {
+ element.textContent = text;
+ }
+
+ // Return built element
+ return element;
+ }
+
+ // Insert a DocumentFragment
+ function insertElement(type, parent, attributes, text) {
+ // Create a new <element>
+ var element = createElement(type, attributes, text);
// Inject the new element
- _prependChild(parent, element);
+ prependChild(parent, element);
+ }
+
+ // Remove all child elements
+ function emptyElement(element) {
+ var length = element.childNodes.length;
+ while (length--) {
+ element.removeChild(element.lastChild);
+ }
}
// Get a classname from selector
- function _getClassname(selector) {
+ function getClassname(selector) {
return selector.replace('.', '');
}
// Toggle class on an element
- function _toggleClass(element, className, state) {
+ function toggleClass(element, className, state) {
if (element) {
if (element.classList) {
element.classList[state ? 'add' : 'remove'](className);
@@ -410,7 +587,7 @@
}
// Has class name
- function _hasClass(element, className) {
+ function hasClass(element, className) {
if (element) {
if (element.classList) {
return element.classList.contains(className);
@@ -422,41 +599,58 @@
}
// Element matches selector
- function _matches(element, selector) {
- var p = Element.prototype;
+ function matches(element, selector) {
+ var prototype = Element.prototype;
+
+ var matches = prototype.matches ||
+ prototype.webkitMatchesSelector ||
+ prototype.mozMatchesSelector ||
+ prototype.msMatchesSelector ||
+ function(s) {
+ return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
+ };
- var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
- return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
- };
+ return matches.call(element, selector);
+ }
+
+ // Get the focused element
+ function getFocusElement() {
+ var focused = document.activeElement;
- return f.call(element, selector);
+ if (!focused || focused === document.body) {
+ focused = null;
+ } else {
+ focused = document.querySelector(':focus');
+ }
+
+ return focused;
}
// Bind along with custom handler
- function _proxyListener(element, eventName, userListener, defaultListener, useCapture) {
- _on(element, eventName, function(event) {
- if (userListener) {
- userListener.apply(element, [event]);
+ function proxy(element, eventName, customListener, defaultListener, useCapture) {
+ on(element, eventName, function(event) {
+ if (customListener) {
+ customListener.apply(element, [event]);
}
defaultListener.apply(element, [event]);
}, useCapture);
}
// Toggle event listener
- function _toggleListener(element, events, callback, toggle, useCapture) {
+ function toggleListener(elements, events, callback, toggle, useCapture) {
var eventList = events.split(' ');
// Whether the listener is a capturing listener or not
// Default to false
- if (!_is.boolean(useCapture)) {
+ if (!is.boolean(useCapture)) {
useCapture = false;
}
// If a nodelist is passed, call itself on each node
- if (element instanceof NodeList) {
- for (var x = 0; x < element.length; x++) {
- if (element[x] instanceof Node) {
- _toggleListener(element[x], arguments[1], arguments[2], arguments[3]);
+ if (elements instanceof NodeList) {
+ for (var x = 0; x < elements.length; x++) {
+ if (elements[x] instanceof Node) {
+ toggleListener(elements[x], arguments[1], arguments[2], arguments[3]);
}
}
return;
@@ -464,40 +658,40 @@
// If a single node is passed, bind the event listener
for (var i = 0; i < eventList.length; i++) {
- element[toggle ? 'addEventListener' : 'removeEventListener'](eventList[i], callback, useCapture);
+ elements[toggle ? 'addEventListener' : 'removeEventListener'](eventList[i], callback, useCapture);
}
}
- // Bind event
- function _on(element, events, callback, useCapture) {
- if (element) {
- _toggleListener(element, events, callback, true, useCapture);
+ // Bind event handler
+ function on(element, events, callback, useCapture) {
+ if (!is.undefined(element)) {
+ toggleListener(element, events, callback, true, useCapture);
}
}
- // Unbind event
- /*function _off(element, events, callback, useCapture) {
- if (element) {
- _toggleListener(element, events, callback, false, useCapture);
+ // Unbind event handler
+ function off(element, events, callback, useCapture) {
+ if (!is.undefined(element)) {
+ toggleListener(element, events, callback, false, useCapture);
}
- }*/
+ }
// Trigger event
- function _event(element, type, bubbles, properties) {
+ function event(element, type, bubbles, properties) {
// Bail if no element
if (!element || !type) {
return;
}
// Default bubbles to false
- if (!_is.boolean(bubbles)) {
+ if (!is.boolean(bubbles)) {
bubbles = false;
}
// Create and dispatch the event
var event = new CustomEvent(type, {
- bubbles: bubbles,
- detail: properties
+ bubbles: bubbles,
+ detail: properties
});
// Dispatch the event
@@ -506,14 +700,14 @@
// Toggle aria-pressed state on a toggle button
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
- function _toggleState(target, state) {
+ function toggleState(target, state) {
// Bail if no target
if (!target) {
return;
}
// Get state
- state = (_is.boolean(state) ? state : !target.getAttribute('aria-pressed'));
+ state = (is.boolean(state) ? state : !target.getAttribute('aria-pressed'));
// Set the attribute on target
target.setAttribute('aria-pressed', state);
@@ -522,7 +716,7 @@
}
// Get percentage
- function _getPercentage(current, max) {
+ function getPercentage(current, max) {
if (current === 0 || max === 0 || isNaN(current) || isNaN(max)) {
return 0;
}
@@ -532,7 +726,7 @@
// Deep extend/merge destination object with N more objects
// http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
// Removed call to arguments.callee (used explicit function name instead)
- function _extend() {
+ function extend() {
// Get arguments
var objects = arguments;
@@ -547,17 +741,25 @@
}
// First object is the destination
- var destination = Array.prototype.shift.call(objects),
- length = objects.length;
+ var destination = Array.prototype.shift.call(objects);
+ if (!is.object(destination)) {
+ destination = {};
+ }
+
+ var length = objects.length;
// Loop through all objects to merge
for (var i = 0; i < length; i++) {
var source = objects[i];
+ if (!is.object(source)) {
+ source = {};
+ }
+
for (var property in source) {
if (source[property] && source[property].constructor && source[property].constructor === Object) {
destination[property] = destination[property] || {};
- _extend(destination[property], source[property]);
+ extend(destination[property], source[property]);
} else {
destination[property] = source[property];
}
@@ -567,84 +769,49 @@
return destination;
}
- // Check variable types
- var _is = {
- object: function(input) {
- return input !== null && typeof(input) === 'object';
- },
- array: function(input) {
- return input !== null && (typeof(input) === 'object' && input.constructor === Array);
- },
- number: function(input) {
- return input !== null && (typeof(input) === 'number' && !isNaN(input - 0) || (typeof input === 'object' && input.constructor === Number));
- },
- string: function(input) {
- return input !== null && (typeof input === 'string' || (typeof input === 'object' && input.constructor === String));
- },
- boolean: function(input) {
- return input !== null && typeof input === 'boolean';
- },
- nodeList: function(input) {
- return input !== null && input instanceof NodeList;
- },
- htmlElement: function(input) {
- return input !== null && input instanceof HTMLElement;
- },
- function: function(input) {
- return input !== null && typeof input === 'function';
- },
- undefined: function(input) {
- return input !== null && typeof input === 'undefined';
- }
- };
-
// Parse YouTube ID from url
- function _parseYouTubeId(url) {
+ function parseYouTubeId(url) {
var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
return (url.match(regex)) ? RegExp.$2 : url;
}
// Fullscreen API
- function _fullscreen() {
- var fullscreen = {
- supportsFullScreen: false,
- isFullScreen: function() { return false; },
- requestFullScreen: function() {},
- cancelFullScreen: function() {},
- fullScreenEventName: '',
- element: null,
- prefix: ''
- },
- browserPrefixes = 'webkit o moz ms khtml'.split(' ');
+ var fullscreen = (function() {
+ // Determine the prefix
+ var prefix = (function() {
+ var value = false;
- // Check for native support
- if (!_is.undefined(document.cancelFullScreen)) {
- fullscreen.supportsFullScreen = true;
- } else {
- // Check for fullscreen support by vendor prefix
- for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
- fullscreen.prefix = browserPrefixes[i];
-
- if (!_is.undefined(document[fullscreen.prefix + 'CancelFullScreen'])) {
- fullscreen.supportsFullScreen = true;
- break;
- } else if (!_is.undefined(document.msExitFullscreen) && document.msFullscreenEnabled) {
- // Special case for MS (when isn't it?)
- fullscreen.prefix = 'ms';
- fullscreen.supportsFullScreen = true;
- break;
- }
+ if (is.function(document.cancelFullScreen)) {
+ value = '';
+ } else {
+ // Check for fullscreen support by vendor prefix
+ ['webkit', 'o', 'moz', 'ms', 'khtml'].some(function(prefix) {
+ if (is.function(document[prefix + 'CancelFullScreen'])) {
+ value = prefix;
+ return true;
+ } else if (is.function(document.msExitFullscreen) && document.msFullscreenEnabled) {
+ // Special case for MS (when isn't it?)
+ value = 'ms';
+ return true;
+ }
+ });
}
- }
- // Update methods to do something useful
- if (fullscreen.supportsFullScreen) {
+ return value;
+ })();
+
+ return {
+ prefix: prefix,
// Yet again Microsoft awesomeness,
// Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
- fullscreen.fullScreenEventName = (fullscreen.prefix === 'ms' ? 'MSFullscreenChange' : fullscreen.prefix + 'fullscreenchange');
+ eventType: (prefix === 'ms' ? 'MSFullscreenChange' : prefix + 'fullscreenchange'),
- fullscreen.isFullScreen = function(element) {
- if (_is.undefined(element)) {
+ // Is an element fullscreen
+ isFullScreen: function(element) {
+ if (!support.fullscreen) {
+ return false;
+ }
+ if (is.undefined(element)) {
element = document.body;
}
switch (this.prefix) {
@@ -653,29 +820,43 @@
case 'moz':
return document.mozFullScreenElement === element;
default:
- return document[this.prefix + 'FullscreenElement'] === element;
+ return document[prefix + 'FullscreenElement'] === element;
}
- };
- fullscreen.requestFullScreen = function(element) {
- if (_is.undefined(element)) {
+ },
+ requestFullScreen: function(element) {
+ if (!support.fullscreen) {
+ return false;
+ }
+ if (!is.htmlElement(element)) {
element = document.body;
}
- return (this.prefix === '') ? element.requestFullScreen() : element[this.prefix + (this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
- };
- fullscreen.cancelFullScreen = function() {
- return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + (this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
- };
- fullscreen.element = function() {
- return (this.prefix === '') ? document.fullscreenElement : document[this.prefix + 'FullscreenElement'];
- };
- }
- return fullscreen;
- }
+ console.log(prefix);
+
+ return (prefix === '') ? element.requestFullScreen() : element[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
+ },
+ cancelFullScreen: function() {
+ if (!support.fullscreen) {
+ return false;
+ }
+ return (prefix === '') ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
+ },
+ element: function() {
+ if (!support.fullscreen) {
+ return null;
+ }
+ return (prefix === '') ? document.fullscreenElement : document[prefix + 'FullscreenElement'];
+ }
+ };
+ })();
- // Local storage
- var _storage = {
- supported: (function() {
+ // Check for support
+ var support = {
+ // Fullscreen support and set prefix
+ fullscreen: fullscreen.prefix !== false,
+ // Local storage mode
+ // We can't assume if local storage is present that we can use it
+ storage: (function() {
if (!('localStorage' in window)) {
return false;
}
@@ -694,334 +875,1087 @@
// Check if value matches
return (result === 'OK');
+ } catch (e) {
+ return false;
}
- catch (e) {
+
+ return false;
+ })(),
+ // Picture-in-picture support
+ // Safari only currently
+ pip: (function() {
+ return is.function(createElement('video').webkitSetPresentationMode);
+ })(),
+ // Airplay support
+ // Safari only currently
+ airplay: (function() {
+ return is.function(window.WebKitPlaybackTargetAvailabilityEvent);
+ })(),
+ // Check for mime type support against a player instance
+ // Credits: http://diveintohtml5.info/everything.html
+ // Related: http://www.leanbackplayer.com/test/h5mt.html
+ mime: function(player, type) {
+ var media = player.media;
+
+ try {
+ // Bail if no checking function
+ if (!is.function(media.canPlayType)) {
+ return false;
+ }
+
+ // Type specific checks
+ if (player.type === 'video') {
+ switch (type) {
+ case 'video/webm':
+ return media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, '');
+ case 'video/mp4':
+ return media.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '');
+ case 'video/ogg':
+ return media.canPlayType('video/ogg; codecs="theora"').replace(/no/, '');
+ }
+ } else if (player.type === 'audio') {
+ switch (type) {
+ case 'audio/mpeg':
+ return media.canPlayType('audio/mpeg;').replace(/no/, '');
+ case 'audio/ogg':
+ return media.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '');
+ case 'audio/wav':
+ return media.canPlayType('audio/wav; codecs="1"').replace(/no/, '');
+ }
+ }
+ } catch (e) {
return false;
}
+ // If we got this far, we're stuffed
return false;
- })()
+ }
};
// Player instance
function Plyr(media, config) {
- var plyr = this,
- timers = {},
- api;
+ var player = this;
+ var timers = {};
+ var api;
+
+ player.fullscreen = {
+ active: false
+ };
+
+ // Elements cache
+ player.elements = {
+ buttons: {},
+ display: {},
+ progress: {},
+ inputs: {},
+ settings: {
+ menu: null,
+ panes: {},
+ tabs: {}
+ },
+ media: media
+ };
+
+ // Captions
+ player.captions = {
+ enabled: false,
+ textTracks: false,
+ captions: []
+ };
// Set media
- plyr.media = media;
var original = media.cloneNode(true);
- // Trigger events, with plyr instance passed
- function _triggerEvent(element, type, bubbles, properties) {
- _event(element, type, bubbles, _extend({}, properties, {
- plyr: api
- }));
- }
-
// Debugging
- function _console(type, args) {
+ function logger(type, args) {
if (config.debug && window.console) {
args = Array.prototype.slice.call(args);
- if (_is.string(config.logPrefix) && config.logPrefix.length) {
+ if (is.string(config.logPrefix) && config.logPrefix.length) {
args.unshift(config.logPrefix);
}
- console[type].apply(console, args);
+ window.console[type].apply(window.console, args);
+ }
+ }
+ var log = function() {
+ logger('log', arguments);
+ };
+ var warn = function() {
+ logger('warn', arguments);
+ };
+ // Log config options and support
+ log('Config', config);
+ log('Support', support);
+
+ // Trigger events, with plyr instance passed
+ function trigger(element, type, bubbles, properties) {
+ event(element, type, bubbles, extend({}, properties, {
+ plyr: api
+ }));
+ }
+
+ // Find all elements
+ function getElements(selector) {
+ return player.elements.container.querySelectorAll(selector);
+ }
+
+ // Find a single element
+ function getElement(selector) {
+ return getElements(selector)[0];
+ }
+
+ // Trap focus inside container
+ function focusTrap() {
+ var tabbables = getElements('input:not([disabled]), button:not([disabled])');
+ var first = tabbables[0];
+ var last = tabbables[tabbables.length - 1];
+
+ function checkFocus(event) {
+ // If it is TAB
+ if (event.which === 9 && player.fullscreen.active) {
+ if (event.target === last && !event.shiftKey) {
+ // Move focus to first element that can be tabbed if Shift isn't used
+ event.preventDefault();
+ first.focus();
+ } else if (event.target === first && event.shiftKey) {
+ // Move focus to last element that can be tabbed if Shift is used
+ event.preventDefault();
+ last.focus();
+ }
+ }
}
+
+ // Bind the handler
+ on(player.elements.container, 'keydown', checkFocus);
}
- var _log = function() { _console('log', arguments) },
- _warn = function() { _console('warn', arguments) };
- // Log config options
- _log('Config', config);
+ // Add elements to HTML5 media (source, tracks, etc)
+ function insertElements(type, attributes) {
+ if (is.string(attributes)) {
+ insertElement(type, player.elements.media, {
+ src: attributes
+ });
+ } else if (is.array(attributes)) {
+ attributes.forEach(function(attribute) {
+ insertElement(type, player.elements.media, attribute);
+ });
+ }
+ }
// Get icon URL
- function _getIconUrl() {
+ function getIconUrl() {
return {
- url: config.iconUrl,
- absolute: (config.iconUrl.indexOf("http") === 0) || plyr.browser.isIE
+ url: config.iconUrl,
+ absolute: (config.iconUrl.indexOf("http") === 0) || player.browser.isIE
};
}
- // Build the default HTML
- function _buildControls() {
- // Create html array
- var html = [],
- iconUrl = _getIconUrl(),
- iconPath = (!iconUrl.absolute ? iconUrl.url : '') + '#' + config.iconPrefix;
+ // Create <svg> icon
+ function createIcon(type, attributes) {
+ var namespace = 'http://www.w3.org/2000/svg';
+ var iconUrl = getIconUrl();
+ var iconPath = (!iconUrl.absolute ? iconUrl.url : '') + '#' + config.iconPrefix;
- // Larger overlaid play button
- if (_inArray(config.controls, 'play-large')) {
- html.push(
- '<button type="button" data-plyr="play" class="plyr__play-large">',
- '<svg><use xlink:href="' + iconPath + '-play" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.play + '</span>',
- '</button>'
- );
+ // Create <svg>
+ var icon = document.createElementNS(namespace, 'svg');
+ setAttributes(icon, extend(attributes, {
+ role: 'presentation'
+ }));
+
+ // Create the <use> to reference sprite
+ var use = document.createElementNS(namespace, 'use');
+ use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', iconPath + '-' + type);
+
+ // Add <use> to <svg>
+ icon.appendChild(use);
+
+ return icon;
+ }
+
+ // Create hidden text label
+ function createLabel(type) {
+ var text = config.i18n[type];
+
+ switch (type) {
+ case 'pip':
+ text = 'PIP';
+ break;
+
+ case 'airplay':
+ text = 'AirPlay';
+ break;
+ }
+
+ return createElement('span', {
+ class: config.classes.hidden
+ }, text);
+ }
+
+ // Create a badge
+ function createBadge(text) {
+ var badge = createElement('span', {
+ class: config.classes.menu.value
+ });
+
+ badge.appendChild(createElement('span', {
+ class: config.classes.menu.badge
+ }, text));
+
+ return badge;
+ }
+
+ // Create a <button>
+ function createButton(type, attributes) {
+ var button = createElement('button');
+ var iconDefault;
+ var iconToggled;
+ var labelKey;
+
+ if (!is.object(attributes)) {
+ attributes = {};
+ }
+
+ if ('class' in attributes) {
+ if (attributes.class.indexOf(config.classes.control) === -1) {
+ attributes.class += ' ' + config.classes.control;
+ }
+ } else {
+ attributes.class = config.classes.control;
+ }
+
+ // Large play button
+ switch (type) {
+ case 'mute':
+ labelKey = 'toggleMute';
+ iconDefault = 'volume';
+ iconToggled = 'muted';
+ break;
+
+ case 'captions':
+ labelKey = 'toggleCaptions';
+ iconDefault = 'captions-off';
+ iconToggled = 'captions-on';
+ break;
+
+ case 'fullscreen':
+ labelKey = 'toggleFullscreen';
+ iconDefault = 'enter-fullscreen';
+ iconToggled = 'exit-fullscreen';
+ break;
+
+ case 'play-large':
+ attributes.class = 'plyr__play-large';
+ type = 'play';
+ labelKey = 'play';
+ iconDefault = 'play';
+ break;
+
+ default:
+ labelKey = type;
+ iconDefault = type;
+ }
+
+ // Merge attributes
+ extend(attributes, getAttributesFromSelector(config.selectors.buttons[type], attributes));
+
+ // Add toggle icon if needed
+ if (is.string(iconToggled)) {
+ button.appendChild(createIcon(iconToggled, {
+ class: 'icon--' + iconToggled
+ }));
}
- html.push('<div class="plyr__controls">');
+ // Add the icon
+ button.appendChild(createIcon(iconDefault));
+
+ // Add the label
+ button.appendChild(createLabel(labelKey));
+
+ // Set element attributes
+ setAttributes(button, attributes);
+
+ player.elements.buttons[type] = button;
+
+ return button;
+ }
+
+ // Create an <input type='range'>
+ function createRange(type, attributes) {
+ // Seek label
+ var label = createElement('label', {
+ for: attributes.id,
+ class: config.classes.hidden
+ }, config.i18n[type]);
+
+ // Seek input
+ var input = createElement('input', extend(getAttributesFromSelector(config.selectors.inputs[type]), {
+ type: 'range',
+ min: 0,
+ max: 100,
+ step: 0.1,
+ value: 0
+ }, attributes));
+
+ player.elements.inputs[type] = input;
+
+ return {
+ label: label,
+ input: input
+ }
+ }
+
+ // Create a <progress>
+ function createProgress(type, attributes) {
+ var progress = createElement('progress', extend(getAttributesFromSelector(config.selectors.display[type]), {
+ min: 0,
+ max: 100,
+ value: 0
+ }, attributes));
+
+ // Create the label inside
+ if (type !== 'volume') {
+ progress.appendChild(createElement('span', null, '0'));
+
+ var suffix = '';
+ switch (type) {
+ case 'played':
+ suffix = config.i18n.played;
+ break;
+
+ case 'buffer':
+ suffix = config.i18n.buffered;
+ break;
+ }
+
+ progress.textContent = '% ' + suffix.toLowerCase();
+ }
+
+ player.elements.display[type] = progress;
+
+ return progress;
+ }
+
+ // Create time display
+ function createTime(type) {
+ var container = createElement('span', {
+ class: 'plyr__time'
+ });
+
+ container.appendChild(createElement('span', {
+ class: config.classes.hidden
+ }, config.i18n[type]));
+
+ container.appendChild(createElement('span', getAttributesFromSelector(config.selectors.display[type]), '00:00'));
+
+ player.elements.display[type] = container;
+
+ return container;
+ }
+
+ // Build the default HTML
+ function createControls(data) {
+ // Create the container
+ var controls = createElement('div', getAttributesFromSelector(config.selectors.controls.wrapper));
// Restart button
- if (_inArray(config.controls, 'restart')) {
- html.push(
- '<button type="button" data-plyr="restart">',
- '<svg><use xlink:href="' + iconPath + '-restart" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.restart + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'restart')) {
+ controls.appendChild(createButton('restart'));
}
// Rewind button
- if (_inArray(config.controls, 'rewind')) {
- html.push(
- '<button type="button" data-plyr="rewind">',
- '<svg><use xlink:href="' + iconPath + '-rewind" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.rewind + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'rewind')) {
+ controls.appendChild(createButton('rewind'));
}
// Play Pause button
// TODO: This should be a toggle button really?
- if (_inArray(config.controls, 'play')) {
- html.push(
- '<button type="button" data-plyr="play">',
- '<svg><use xlink:href="' + iconPath + '-play" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.play + '</span>',
- '</button>',
- '<button type="button" data-plyr="pause">',
- '<svg><use xlink:href="' + iconPath + '-pause" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.pause + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'play')) {
+ controls.appendChild(createButton('play'));
+ controls.appendChild(createButton('pause'));
}
// Fast forward button
- if (_inArray(config.controls, 'fast-forward')) {
- html.push(
- '<button type="button" data-plyr="fast-forward">',
- '<svg><use xlink:href="' + iconPath + '-fast-forward" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.forward + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'fast-forward')) {
+ controls.appendChild(createButton('fast-forward'));
}
// Progress
- if (_inArray(config.controls, 'progress')) {
- // Create progress
- html.push('<span class="plyr__progress">',
- '<label for="seek{id}" class="plyr__sr-only">Seek</label>',
- '<input id="seek{id}" class="plyr__progress--seek" type="range" min="0" max="100" step="0.1" value="0" data-plyr="seek">',
- '<progress class="plyr__progress--played" max="100" value="0" role="presentation"></progress>',
- '<progress class="plyr__progress--buffer" max="100" value="0">',
- '<span>0</span>% ' + config.i18n.buffered,
- '</progress>');
+ if (inArray(config.controls, 'progress')) {
+ var container = createElement('span', getAttributesFromSelector(config.selectors.progress));
+
+ // Seek range slider
+ var seek = createRange('seek', {
+ id: 'plyr-seek-' + data.id
+ });
+ container.appendChild(seek.label);
+ container.appendChild(seek.input);
+
+ // TODO: Add loop display indicator
+
+ // Played progress
+ container.appendChild(createProgress('played'));
+
+ // Buffer progress
+ container.appendChild(createProgress('buffer'));
// Seek tooltip
if (config.tooltips.seek) {
- html.push('<span class="plyr__tooltip">00:00</span>');
+ var tooltip = createElement('span', {
+ role: 'tooltip',
+ class: config.classes.tooltip
+ }, '00:00');
+
+ container.appendChild(tooltip);
+ player.elements.display.seekTooltip = tooltip;
}
- // Close
- html.push('</span>');
+ player.elements.progress = container;
+ controls.appendChild(player.elements.progress);
}
// Media current time display
- if (_inArray(config.controls, 'current-time')) {
- html.push(
- '<span class="plyr__time">',
- '<span class="plyr__sr-only">' + config.i18n.currentTime + '</span>',
- '<span class="plyr__time--current">00:00</span>',
- '</span>'
- );
+ if (inArray(config.controls, 'current-time')) {
+ controls.appendChild(createTime('currentTime'));
}
// Media duration display
- if (_inArray(config.controls, 'duration')) {
- html.push(
- '<span class="plyr__time">',
- '<span class="plyr__sr-only">' + config.i18n.duration + '</span>',
- '<span class="plyr__time--duration">00:00</span>',
- '</span>'
- );
+ if (inArray(config.controls, 'duration')) {
+ controls.appendChild(createTime('duration'));
}
// Toggle mute button
- if (_inArray(config.controls, 'mute')) {
- html.push(
- '<button type="button" data-plyr="mute">',
- '<svg class="icon--muted"><use xlink:href="' + iconPath + '-muted" /></svg>',
- '<svg><use xlink:href="' + iconPath + '-volume" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.toggleMute + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'mute')) {
+ controls.appendChild(createButton('mute'));
}
// Volume range control
- if (_inArray(config.controls, 'volume')) {
- html.push(
- '<span class="plyr__volume">',
- '<label for="volume{id}" class="plyr__sr-only">' + config.i18n.volume + '</label>',
- '<input id="volume{id}" class="plyr__volume--input" type="range" min="' + config.volumeMin + '" max="' + config.volumeMax + '" value="' + config.volume + '" data-plyr="volume">',
- '<progress class="plyr__volume--display" max="' + config.volumeMax + '" value="' + config.volumeMin + '" role="presentation"></progress>',
- '</span>'
- );
+ if (inArray(config.controls, 'volume')) {
+ var volume = createElement('span', {
+ class: 'plyr__volume'
+ });
+
+ // Set the attributes
+ var attributes = {
+ max: 10,
+ value: config.volume
+ };
+
+ // Create the volume range slider
+ var range = createRange('volume', extend(attributes, {
+ id: 'plyr-volume-' + data.id
+ }));
+ volume.appendChild(range.label);
+ volume.appendChild(range.input);
+
+ // Create the display progress
+ var progress = createProgress('volume', attributes);
+ volume.appendChild(progress);
+
+ controls.appendChild(volume);
}
// Toggle captions button
- if (_inArray(config.controls, 'captions')) {
- html.push(
- '<button type="button" data-plyr="captions">',
- '<svg class="icon--captions-on"><use xlink:href="' + iconPath + '-captions-on" /></svg>',
- '<svg><use xlink:href="' + iconPath+ '-captions-off" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.toggleCaptions + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'captions')) {
+ controls.appendChild(createButton('captions'));
+ }
+
+ // Settings button / menu
+ if (inArray(config.controls, 'settings')) {
+ var menu = createElement('span', extend(getAttributesFromSelector(config.selectors.buttons.settings), {
+ class: 'plyr__menu'
+ }));
+
+ menu.appendChild(createButton('settings', {
+ id: 'plyr-settings-toggle-' + data.id,
+ 'aria-haspopup': true,
+ 'aria-controls': 'plyr-settings-' + data.id,
+ 'aria-expanded': false
+ }));
+
+ var form = createElement('form', {
+ class: 'plyr__menu__container',
+ id: 'plyr-settings-' + data.id,
+ 'aria-hidden': true,
+ 'aria-labelled-by': 'plyr-settings-toggle-' + data.id,
+ role: 'tablist',
+ tabindex: -1
+ });
+
+ var inner = createElement('div');
+
+ var home = createElement('div', {
+ id: 'plyr-settings-' + data.id + '-home',
+ 'aria-hidden': false,
+ 'aria-labelled-by': 'plyr-settings-toggle-' + data.id,
+ role: 'tabpanel',
+ tabindex: -1
+ });
+
+ var tabs = createElement('ul', {
+ role: 'tablist'
+ });
+
+ ['captions', 'quality', 'speed', 'loop'].forEach(function(type) {
+ var tab = createElement('li', {
+ role: 'tab'
+ });
+
+ var button = createElement('button', extend(getAttributesFromSelector(config.selectors.buttons.settings), {
+ type: 'button',
+ class: config.classes.control + ' ' + config.classes.control + '--forward',
+ id: 'plyr-settings-' + data.id + '-' + type + '-tab',
+ 'aria-haspopup': true,
+ 'aria-controls': 'plyr-settings-' + data.id + '-' + type,
+ 'aria-expanded': false
+ }), config.i18n[type]);
+
+ var value = createElement('span', {
+ class: config.classes.menu.value
+ });
+
+ // Speed contains HTML entities
+ value.innerHTML = data[type];
+
+ button.appendChild(value);
+
+ tab.appendChild(button);
+
+ tabs.appendChild(tab);
+
+ player.elements.settings.tabs[type] = tab;
+ });
+
+ home.appendChild(tabs);
+
+ inner.appendChild(home);
+
+ ['captions', 'quality', 'speed', 'loop'].forEach(function(type) {
+ var pane = createElement('div', {
+ id: 'plyr-settings-' + data.id + '-' + type,
+ 'aria-hidden': true,
+ 'aria-labelled-by': 'plyr-settings-tab-' + data.id,
+ role: 'tabpanel',
+ tabindex: -1
+ });
+
+ var back = createElement('button', {
+ type: 'button',
+ class: config.classes.control + ' ' + config.classes.control + '--back',
+ 'aria-haspopup': true,
+ 'aria-controls': 'plyr-settings-' + data.id + '-home',
+ 'aria-expanded': false
+ }, config.i18n[type]);
+
+ pane.appendChild(back);
+
+ var options = createElement('ul');
+
+ /*switch (type) {
+ case 'captions':
+ if (is.array(config.tracks)) {
+ config.tracks.forEach(function(track, index) {
+ if (is.function(track)) {
+ return;
+ }
+
+ var option = createElement('li');
+
+ var button = createButton('language', {
+ 'data-language': track.srclang,
+ 'data-index': index
+ }, track.label);
+
+ option.appendChild(button);
+
+ options.appendChild(options);
+ });
+ }
+ break;
+ }*/
+
+ pane.appendChild(options);
+
+ inner.appendChild(pane);
+
+ player.elements.settings.panes[type] = pane;
+ });
+
+ form.appendChild(inner);
+
+ menu.appendChild(form);
+
+ controls.appendChild(menu);
+
+ player.elements.settings.menu = menu;
+
+ /*html.push(
+ '<div class="plyr__menu" data-plyr="settings">',
+ '<button type="button" id="plyr-settings-toggle-{id}" class="plyr__control" aria-haspopup="true" aria-controls="plyr-settings-{id}" aria-expanded="false">',
+ '<svg><use xlink:href="' + iconPath + '-settings" /></svg>',
+ '<span class="plyr__sr-only">' + config.i18n.settings + '</span>',
+ '</button>',
+ '<form class="plyr__menu__container" id="plyr-settings-{id}" aria-hidden="true" aria-labelled-by="plyr-settings-toggle-{id}" role="tablist" tabindex="-1">',
+ '<div>',
+ '<div id="plyr-settings-{id}-primary" aria-hidden="false" aria-labelled-by="plyr-settings-toggle-{id}" role="tabpanel" tabindex="-1">',
+ '<ul>',
+ captionsMenuItem,
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--forward" id="plyr-settings-{id}-speed-toggle" aria-haspopup="true" aria-controls="plyr-settings-{id}-speed" aria-expanded="false">',
+ config.i18n.speed +
+ '<span class="plyr__menu__value" data-menu="speed">{speed}</span>',
+ '</button>',
+ '</li>',
+ '<li role="tab">',
+
+ //showQuality,
+
+ '<button type="button" class="plyr__control plyr__control--forward" id="plyr-settings-{id}-quality-toggle" aria-haspopup="true" aria-controls="plyr-settings-{id}-quality" aria-expanded="false">',
+ config.i18n.quality,
+ '<span class="plyr__menu__value">{quality}</span>',
+ '</button>',
+
+ '</li>',
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--forward" id="plyr-settings-{id}-loop-toggle" aria-haspopup="true" aria-controls="plyr-settings-{id}-loop" aria-expanded="false">',
+ config.i18n.loop +
+ '<span class="plyr__menu__value" data-menu="loop">{loop}</span>',
+ '</button>',
+ '</li>',
+ '</ul>',
+ '</div>',
+ '<div id="plyr-settings-{id}-captions" aria-hidden="true" aria-labelled-by="plyr-settings-{id}-captions-toggle" role="tabpanel" tabindex="-1">',
+ '<ul>',
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--back" aria-haspopup="true" aria-controls="plyr-settings-{id}-primary" aria-expanded="false">',
+ config.i18n.captions,
+ '</button>',
+ '</li>',
+ '<li data-captions="langs">',
+ buildCaptionsMenu(),
+ '</li>',
+ '<li>',
+ '<button type="button" class="plyr__control" data-plyr="captions_menu">Off</button>',
+ '</li>',
+ '</ul>',
+ '</div>',
+ '<div id="plyr-settings-{id}-speed" aria-hidden="true" aria-labelled-by="plyr-settings-{id}-speed-toggle" role="tabpanel" tabindex="-1">',
+ '<ul>',
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--back" aria-haspopup="true" aria-controls="plyr-settings-{id}-primary" aria-expanded="false">',
+ config.i18n.speed,
+ '</button>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="speed" data-plyr="speed" value="2.0" '+ (config.currentSpeed === 2 ? 'checked' : '') +'>',
+ '2.0&times;',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="speed" data-plyr="speed" value="1.5" '+ (config.currentSpeed === 1.5 ? 'checked' : '') +'>',
+ '1.5&times;',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="speed" data-plyr="speed" value="1.0" '+ (config.currentSpeed === 1 ? 'checked' : '') +'>',
+ '1.0&times;',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="speed" data-plyr="speed" value="0.5" '+ (config.currentSpeed === 0.5 ? 'checked' : '') +'>',
+ '0.5&times;',
+ '</label>',
+ '</li>',
+ '</ul>',
+ '</div>',
+ '<div id="plyr-settings-{id}-quality" aria-hidden="true" aria-labelled-by="plyr-settings-{id}-quality-toggle" role="tabpanel" tabindex="-1">',
+ '<ul>',
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--back" aria-haspopup="true" aria-controls="plyr-settings-{id}-primary" aria-expanded="false">',
+ config.i18n.quality,
+ '</button>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '2160P',
+ '<span class="plyr__menu__value">',
+ '<span class="plyr__badge">4K</span>',
+ '</span>',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '1440P',
+ '<span class="plyr__menu__value">',
+ '<span class="plyr__badge">WQHD</span>',
+ '</span>',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '1080P',
+ '<span class="plyr__menu__value">',
+ '<span class="plyr__badge">HD</span>',
+ '</span>',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '720P',
+ '<span class="plyr__menu__value">',
+ '<span class="plyr__badge">HD</span>',
+ '</span>',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '480P',
+ '</label>',
+ '</li>',
+ '<li>',
+ '<label class="plyr__control">',
+ '<input type="radio" name="quality">',
+ '360P',
+ '</label>',
+ '</li>',
+ '</ul>',
+ '</div>',
+ '<div id="plyr-settings-{id}-loop" aria-hidden="true" aria-labelled-by="plyr-settings-{id}-loop-toggle" role="tabpanel" tabindex="-1">',
+ '<ul>',
+ '<li role="tab">',
+ '<button type="button" class="plyr__control plyr__control--back" aria-haspopup="true" aria-controls="plyr-settings-{id}-primary" aria-expanded="false">',
+ config.i18n.loop,
+ '</button>',
+ '</li>',
+ '<li>',
+ '<button type="button" class="plyr__control" data-plyr="loop" data-plyr-loop="all">',
+ config.i18n.loopAll,
+ '<span></span>',
+ '</button>',
+ '</li>',
+ '<li>',
+ '<button type="button" class="plyr__control" data-plyr="loop" data-plyr-loop="start">',
+ config.i18n.loopStart,
+ '<span></span>',
+ '</button>',
+ '</li>',
+ '<li>',
+ '<button type="button" class="plyr__control" data-plyr="loop" data-plyr-loop="end">',
+ config.i18n.loopEnd,
+ '<span></span>',
+ '</button>',
+ '</li>',
+ '<li>',
+ '<button type="button" class="plyr__control" data-plyr="loop" data-plyr-loop="none">',
+ config.i18n.loopNone,
+ '</button>',
+ '</li>',
+ '</ul>',
+ '</div>',
+ '</div>',
+ '</form>',
+ '</div>'
+ ); */
+ }
+
+ // Picture in picture button
+ if (inArray(config.controls, 'pip') && support.pip) {
+ controls.appendChild(createButton('pip'));
+ }
+
+ // Airplay button
+ if (inArray(config.controls, 'airplay') && support.airplay) {
+ controls.appendChild(createButton('airplay'));
}
// Toggle fullscreen button
- if (_inArray(config.controls, 'fullscreen')) {
- html.push(
- '<button type="button" data-plyr="fullscreen">',
- '<svg class="icon--exit-fullscreen"><use xlink:href="' + iconPath + '-exit-fullscreen" /></svg>',
- '<svg><use xlink:href="' + iconPath + '-enter-fullscreen" /></svg>',
- '<span class="plyr__sr-only">' + config.i18n.toggleFullscreen + '</span>',
- '</button>'
- );
+ if (inArray(config.controls, 'fullscreen')) {
+ controls.appendChild(createButton('fullscreen'));
+ }
+
+ player.elements.controls = controls;
+
+ setLoopMenu();
+
+ return controls;
+ }
+
+ // Set the YouTube quality menu
+ // TODO: Support for HTML5
+ // YouTube: "hd2160", "hd1440", "hd1080", "hd720", "large", "medium", "small", "tiny", "auto"
+ function setQualityMenu(available, current) {
+ if (is.object(player.quality)) {
+ return;
+ }
+
+ player.quality = {
+ available: available,
+ current: current
+ };
+
+ // Get the badge HTML for HD, 4K etc
+ function getBadge(quality) {
+ var label = "";
+
+ switch (quality) {
+ case 'hd2160':
+ label = '4K';
+ break;
+ case 'hd1440':
+ label = 'WQHD';
+ break;
+ case 'hd1080':
+ label = 'HD';
+ break;
+ case 'hd720':
+ label = 'HD';
+ break;
+ }
+
+ if (!label.length) {
+ return null;
+ }
+
+ return createBadge(label);
+ }
+
+ // Translate the quality key into a nice label
+ function getLabel(quality) {
+ switch (quality) {
+ case 'hd2160':
+ return '2160P';
+ case 'hd1440':
+ return '1440P';
+ case 'hd1080':
+ return '1080P';
+ case 'hd720':
+ return '720P';
+ case 'large':
+ return '480P';
+ case 'medium':
+ return '360P';
+ case 'small':
+ return '240P';
+ default:
+ return 'Auto';
+ }
+ }
+
+ if (is.array(available) && available.length) {
+ // Remove any unwanted quality levels
+ var filtered = available.filter(function(quality) {
+ return ['tiny', 'small'].indexOf(quality) === -1;
+ });
+
+ var list = player.elements.settings.panes.quality.querySelector('ul');
+
+ filtered.forEach(function(quality) {
+ var item = createElement('li');
+
+ var label = createElement('label', {
+ class: config.classes.control
+ });
+
+ var radio = createElement('input', {
+ type: 'radio',
+ name: 'quality',
+ value: quality,
+ });
+
+ if (quality === player.quality.current) {
+ radio.setAttribute('checked', '');
+ }
+
+ label.appendChild(radio);
+ label.appendChild(document.createTextNode(getLabel(quality)));
+
+ var badge = getBadge(quality);
+ if (is.htmlElement(badge)) {
+ label.appendChild(badge);
+ }
+
+ item.appendChild(label);
+
+ list.appendChild(item);
+ });
}
+ }
+
+ // Set the looping options
+ function setLoopMenu() {
+ var options = ['start', 'end', 'all', 'reset'];
+ var list = player.elements.settings.panes.loop.querySelector('ul');
- // Close everything
- html.push('</div>');
+ options.forEach(function(option) {
+ var item = createElement('li');
+
+ var button = createElement('button', {
+ type: 'button',
+ class: config.classes.control,
+ 'data-plyr': 'loop',
+ 'data-plyr-loop-action': option
+ }, config.i18n[option]);
+
+ if (inArray(['start', 'end'], option)) {
+ var badge = createBadge('0:00');
+ button.appendChild(badge);
+ }
- return html.join('');
+ item.appendChild(button);
+
+ list.appendChild(item);
+ });
}
// Setup fullscreen
- function _setupFullscreen() {
- if (!plyr.supported.full) {
+ function setupFullscreen() {
+ if (!player.supported.full) {
return;
}
- if ((plyr.type !== 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) {
+ if ((player.type !== 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) {
// Check for native support
- var nativeSupport = fullscreen.supportsFullScreen;
+ var nativeSupport = support.fullscreen;
- if (nativeSupport || (config.fullscreen.fallback && !_inFrame())) {
- _log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled');
+ if (nativeSupport || (config.fullscreen.fallback && !inFrame())) {
+ log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled');
// Add styling hook
- _toggleClass(plyr.container, config.classes.fullscreen.enabled, true);
+ toggleClass(player.elements.container, config.classes.fullscreen.enabled, true);
} else {
- _log('Fullscreen not supported and fallback disabled');
+ log('Fullscreen not supported and fallback disabled');
}
// Toggle state
- if (plyr.buttons && plyr.buttons.fullscreen) {
- _toggleState(plyr.buttons.fullscreen, false);
+ if (player.elements.buttons && player.elements.buttons.fullscreen) {
+ toggleState(player.elements.buttons.fullscreen, false);
}
// Setup focus trap
- _focusTrap();
+ focusTrap();
+ }
+ }
+
+ // Display active caption if it contains text
+ function setActiveCue(track) {
+ // Get the track from the event if needed
+ if (is.event(track)) {
+ track = track.target;
+ }
+
+ // Display a cue, if there is one
+ if (track.activeCues[0] && 'text' in track.activeCues[0]) {
+ setCaption(track.activeCues[0].getCueAsHTML());
+ } else {
+ setCaption();
}
}
// Setup captions
- function _setupCaptions() {
+ function setupCaptions() {
// Bail if not HTML5 video
- if (plyr.type !== 'video') {
+ if (player.type !== 'video') {
return;
}
// Inject the container
- if (!_getElement(config.selectors.captions)) {
- plyr.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + _getClassname(config.selectors.captions) + '"></div>');
+ if (!getElement(config.selectors.captions)) {
+ player.elements.wrapper.insertAdjacentHTML('afterbegin', '<div class="' + getClassname(config.selectors.captions) + '"></div>');
}
// Determine if HTML5 textTracks is supported
- plyr.usingTextTracks = false;
- if (plyr.media.textTracks) {
- plyr.usingTextTracks = true;
+ player.captions.textTracks = false;
+ if (player.elements.media.textTracks) {
+ player.captions.textTracks = true;
}
// Get URL of caption file if exists
- var captionSrc = '',
- kind,
- children = plyr.media.childNodes;
-
- for (var i = 0; i < children.length; i++) {
- if (children[i].nodeName.toLowerCase() === 'track') {
- kind = children[i].kind;
- if (kind === 'captions' || kind === 'subtitles') {
- captionSrc = children[i].getAttribute('src');
+ var captionSources = [];
+ var captionSrc = '';
+
+ player.elements.media.childNodes.forEach(function(child) {
+ if (child.nodeName.toLowerCase() === 'track') {
+ if (child.kind === 'captions' || child.kind === 'subtitles') {
+ captionSources.push(child.getAttribute('src'));
}
}
- }
+ });
// Record if caption file exists or not
- plyr.captionExists = true;
- if (captionSrc === '') {
- plyr.captionExists = false;
- _log('No caption track found');
+ player.captions.exist = true;
+ if (captionSources.length === 0) {
+ player.captions.exist = false;
+ log('No caption track found');
+ } else if ((Number(config.captions.selectedIndex) + 1) > captionSources.length) {
+ player.captions.exist = false;
+ log('Caption index out of bound');
} else {
- _log('Caption track found; URI: ' + captionSrc);
+ captionSrc = captionSources[config.captions.selectedIndex];
+ log('Caption track found; URI: ' + captionSrc);
}
// If no caption file exists, hide container for caption text
- if (!plyr.captionExists) {
- _toggleClass(plyr.container, config.classes.captions.enabled);
+ if (!player.captions.exist) {
+ toggleClass(player.elements.container, config.classes.captions.enabled);
} else {
+ var tracks = player.elements.media.textTracks;
+
// Turn off native caption rendering to avoid double captions
// This doesn't seem to work in Safari 7+, so the <track> elements are removed from the dom below
- var tracks = plyr.media.textTracks;
- for (var x = 0; x < tracks.length; x++) {
- tracks[x].mode = 'hidden';
- }
+ [].forEach.call(tracks, function(track) {
+ // Remove the listener to prevent event overlapping
+ off(track, 'cuechange', setActiveCue);
+
+ // Hide captions
+ track.mode = 'hidden';
+ });
// Enable UI
- _showCaptions(plyr);
+ showCaptions(player);
// Disable unsupported browsers than report false positive
// Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1033144
- if ((plyr.browser.isIE && plyr.browser.version >= 10) ||
- (plyr.browser.isFirefox && plyr.browser.version >= 31)) {
+ if ((player.browser.isIE && player.browser.version >= 10) ||
+ (player.browser.isFirefox && player.browser.version >= 31)) {
// Debugging
- _log('Detected browser with known TextTrack issues - using manual fallback');
+ log('Detected browser with known TextTrack issues - using manual fallback');
// Set to false so skips to 'manual' captioning
- plyr.usingTextTracks = false;
+ player.captions.textTracks = false;
}
// Rendering caption tracks
// Native support required - http://caniuse.com/webvtt
- if (plyr.usingTextTracks) {
- _log('TextTracks supported');
+ if (player.captions.textTracks) {
+ log('TextTracks supported');
- for (var y = 0; y < tracks.length; y++) {
- var track = tracks[y];
+ var track = tracks[config.captions.selectedIndex];
- if (track.kind === 'captions' || track.kind === 'subtitles') {
- _on(track, 'cuechange', function() {
- // Display a cue, if there is one
- if (this.activeCues[0] && 'text' in this.activeCues[0]) {
- _setCaption(this.activeCues[0].getCueAsHTML());
- } else {
- _setCaption();
- }
- });
+ if (track.kind === 'captions' || track.kind === 'subtitles') {
+ on(track, 'cuechange', setActiveCue);
+
+ // If we change the active track while a cue is already displayed we need to update it
+ if (track.activeCues && track.activeCues.length > 0) {
+ setActiveCue(track);
}
}
} else {
// Caption tracks not natively supported
- _log('TextTracks not supported so rendering captions manually');
+ log('TextTracks not supported so rendering captions manually');
// Render captions from array at appropriate time
- plyr.currentCaption = '';
- plyr.captions = [];
+ player.captions.current = '';
+ player.captions.captions = [];
if (captionSrc !== '') {
// Create XMLHttpRequest Object
@@ -1030,45 +1964,38 @@
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
- var captions = [],
- caption,
- req = xhr.responseText;
+ var response = xhr.responseText;
- //According to webvtt spec, line terminator consists of one of the following
+ // According to webvtt spec, line terminator consists of one of the following
// CRLF (U+000D U+000A), LF (U+000A) or CR (U+000D)
var lineSeparator = '\r\n';
- if(req.indexOf(lineSeparator+lineSeparator) === -1) {
- if(req.indexOf('\r\r') !== -1){
+ if (response.indexOf(lineSeparator + lineSeparator) === -1) {
+ if (response.indexOf('\r\r') !== -1) {
lineSeparator = '\r';
} else {
lineSeparator = '\n';
}
}
- captions = req.split(lineSeparator+lineSeparator);
-
- for (var r = 0; r < captions.length; r++) {
- caption = captions[r];
- plyr.captions[r] = [];
+ var captions = response.split(lineSeparator + lineSeparator);
- // Get the parts of the captions
- var parts = caption.split(lineSeparator),
- index = 0;
+ player.captions.captions = captions.map(function(caption) {
+ var parts = caption.split(lineSeparator);
+ var index = 0;
// Incase caption numbers are added
- if (parts[index].indexOf(":") === -1) {
+ if (parts[index].indexOf(":") !== -1) {
index = 1;
}
- plyr.captions[r] = [parts[index], parts[index + 1]];
- }
+ return [parts[index], parts[index + 1]];
+ });
- // Remove first element ('VTT')
- plyr.captions.shift();
+ player.captions.captions.shift();
- _log('Successfully loaded the caption file via AJAX');
+ log('Successfully loaded the caption file via AJAX');
} else {
- _warn(config.logPrefix + 'There was a problem loading the caption file via AJAX');
+ warn(config.logPrefix + 'There was a problem loading the caption file via AJAX');
}
}
};
@@ -1081,350 +2008,377 @@
}
}
- // Set the current caption
- function _setCaption(caption) {
- /* jshint unused:false */
- var container = _getElement(config.selectors.captions),
- content = document.createElement('span');
+ // Select active caption
+ function setCaptionIndex(index) {
+ // Save active caption
+ config.captions.selectedIndex = index || config.captions.selectedIndex;
+
+ // Clear caption
+ setCaption();
- // Empty the container
- container.innerHTML = '';
+ // Re-run setup
+ setupCaptions();
- // Default to empty
- if (_is.undefined(caption)) {
- caption = '';
+ //getElement('[data-captions="settings"]').innerHTML = getSelectedLanguage();
+ }
+
+ // Get current selected caption language
+ function getSelectedLanguage() {
+ if (config.tracks.length === 0) {
+ return 'No Subs';
}
- // Set the span content
- if (_is.string(caption)) {
- content.innerHTML = caption.trim();
+ if (player.captions.enabled || !is.boolean(player.captions.enabled) && player.storage.captions) {
+ return config.tracks[config.captions.selectedIndex].label;
} else {
- content.appendChild(caption);
+ return 'Disabled';
}
+ }
+
+ // Set the current caption
+ function setCaption(caption) {
+ var captions = getElement(config.selectors.captions);
- // Set new caption text
- container.appendChild(content);
+ if (is.htmlElement(captions)) {
+ var content = createElement('span');
- // Force redraw (for Safari)
- var redraw = container.offsetHeight;
+ // Empty the container
+ emptyElement(captions);
+
+ // Default to empty
+ if (is.undefined(caption)) {
+ caption = '';
+ }
+
+ // Set the span content
+ if (is.string(caption)) {
+ content.innerHTML = caption.trim();
+ } else {
+ content.appendChild(caption);
+ }
+
+ // Set new caption text
+ captions.appendChild(content);
+
+ // Force redraw (for Safari)
+ // var redraw = captions.offsetHeight;
+ }
}
// Captions functions
// Seek the manual caption time and update UI
- function _seekManualCaptions(time) {
+ function seekManualCaptions(time) {
// Utilities for caption time codes
- function _timecodeCommon(tc, pos) {
- var tcpair = [];
- tcpair = tc.split(' --> ');
- for(var i = 0; i < tcpair.length; i++) {
+ function timecodeCommon(timecode, pos) {
+ var parts = [];
+ parts = timecode.split(' --> ');
+ for (var i = 0; i < parts.length; i++) {
// WebVTT allows for extra meta data after the timestamp line
// So get rid of this if it exists
- tcpair[i] = tcpair[i].replace(/(\d+:\d+:\d+\.\d+).*/, "$1");
+ parts[i] = parts[i].replace(/(\d+:\d+:\d+\.\d+).*/, "$1");
}
- return _subTcSecs(tcpair[pos]);
+ return subTcSecs(parts[pos]);
}
- function _timecodeMin(tc) {
- return _timecodeCommon(tc, 0);
+
+ function timecodeMin(timecode) {
+ return timecodeCommon(timecode, 0);
}
- function _timecodeMax(tc) {
- return _timecodeCommon(tc, 1);
+
+ function timecodeMax(timecode) {
+ return timecodeCommon(timecode, 1);
}
- function _subTcSecs(tc) {
- if (tc === null || tc === undefined) {
+
+ function subTcSecs(timecode) {
+ if (is.undefined(timecode)) {
return 0;
} else {
- var tc1 = [],
- tc2 = [],
- seconds;
- tc1 = tc.split(',');
+ var tc1 = [];
+ var tc2 = [];
+ var seconds = 0;
+ tc1 = timecode.split(',');
tc2 = tc1[0].split(':');
- seconds = Math.floor(tc2[0]*60*60) + Math.floor(tc2[1]*60) + Math.floor(tc2[2]);
+
+ for (var i = 0, len = tc2.length; i < len; i++) {
+ seconds += Math.floor(tc2[i] * (Math.pow(60, len - (i + 1))));
+ }
+
return seconds;
}
}
// If it's not video, or we're using textTracks, bail.
- if (plyr.usingTextTracks || plyr.type !== 'video' || !plyr.supported.full) {
+ if (player.captions.textTracks || player.type !== 'video' || !player.supported.full) {
return;
}
// Reset subcount
- plyr.subcount = 0;
+ player.captions.count = 0;
// Check time is a number, if not use currentTime
// IE has a bug where currentTime doesn't go to 0
// https://twitter.com/Sam_Potts/status/573715746506731521
- time = _is.number(time) ? time : plyr.media.currentTime;
+ time = is.number(time) ? time : player.elements.media.currentTime;
// If there's no subs available, bail
- if (!plyr.captions[plyr.subcount]) {
+ if (!player.captions.captions[player.captions.count]) {
return;
}
- while (_timecodeMax(plyr.captions[plyr.subcount][0]) < time.toFixed(1)) {
- plyr.subcount++;
- if (plyr.subcount > plyr.captions.length - 1) {
- plyr.subcount = plyr.captions.length - 1;
+ while (timecodeMax(player.captions.captions[player.captions.count][0]) < time.toFixed(1)) {
+ player.captions.count++;
+
+ if (player.captions.count > player.captions.captions.length - 1) {
+ player.captions.count = player.captions.captions.length - 1;
break;
}
}
// Check if the next caption is in the current time range
- if (plyr.media.currentTime.toFixed(1) >= _timecodeMin(plyr.captions[plyr.subcount][0]) &&
- plyr.media.currentTime.toFixed(1) <= _timecodeMax(plyr.captions[plyr.subcount][0])) {
- plyr.currentCaption = plyr.captions[plyr.subcount][1];
+ if (player.elements.media.currentTime.toFixed(1) >= timecodeMin(player.captions[player.subcount][0]) &&
+ player.elements.media.currentTime.toFixed(1) <= timecodeMax(player.captions[player.subcount][0])) {
+ player.captions.current = player.captions.captions[player.captions.count][1];
// Render the caption
- _setCaption(plyr.currentCaption);
+ setCaption(player.captions.current);
} else {
- _setCaption();
+ setCaption();
}
}
// Display captions container and button (for initialization)
- function _showCaptions() {
+ function showCaptions() {
// If there's no caption toggle, bail
- if (!plyr.buttons.captions) {
+ if (!player.elements.buttons.captions) {
return;
}
- _toggleClass(plyr.container, config.classes.captions.enabled, true);
+ toggleClass(player.elements.container, config.classes.captions.enabled, true);
// Try to load the value from storage
- var active = plyr.storage.captionsEnabled;
+ var active = player.storage.captions;
// Otherwise fall back to the default config
- if (!_is.boolean(active)) {
+ if (!is.boolean(active)) {
active = config.captions.defaultActive;
}
if (active) {
- _toggleClass(plyr.container, config.classes.captions.active, true);
- _toggleState(plyr.buttons.captions, true);
+ toggleClass(player.elements.container, config.classes.captions.active, true);
+ toggleState(player.elements.buttons.captions, true);
}
}
- // Find all elements
- function _getElements(selector) {
- return plyr.container.querySelectorAll(selector);
- }
-
- // Find a single element
- function _getElement(selector) {
- return _getElements(selector)[0];
- }
-
- // Determine if we're in an iframe
- function _inFrame() {
- try {
- return window.self !== window.top;
+ // Toggle captions
+ function toggleCaptions(show) {
+ // If there's no full support, or there's no caption toggle
+ if (!player.supported.full || !player.elements.buttons.captions) {
+ return;
}
- catch (e) {
- return true;
+
+ // If the method is called without parameter, toggle based on current value
+ if (!is.boolean(show)) {
+ show = (player.elements.container.className.indexOf(config.classes.captions.active) === -1);
}
- }
- // Trap focus inside container
- function _focusTrap() {
- var tabbables = _getElements('input:not([disabled]), button:not([disabled])'),
- first = tabbables[0],
- last = tabbables[tabbables.length - 1];
+ // Set global
+ player.captions.enabled = show;
+ //player.elements.buttons.captions_menu.innerHTML = show ? 'Off' : 'On';
+ //TODO: display lang getElement('[data-captions="settings"]').innerHTML = getSubsLangValue();
- function _checkFocus(event) {
- // If it is TAB
- if (event.which === 9 && plyr.isFullscreen) {
- if (event.target === last && !event.shiftKey) {
- // Move focus to first element that can be tabbed if Shift isn't used
- event.preventDefault();
- first.focus();
- } else if (event.target === first && event.shiftKey) {
- // Move focus to last element that can be tabbed if Shift is used
- event.preventDefault();
- last.focus();
- }
- }
- }
+ // Set current language etc
+ //elements.buttons.captions_menu.innerHTML = show ? 'Off' : 'On';
+ //getElement('[data-captions="settings"]').innerHTML = getSubsLangValue();
- // Bind the handler
- _on(plyr.container, 'keydown', _checkFocus);
- }
+ // Toggle state
+ toggleState(player.elements.buttons.captions, player.captions.enabled);
- // Add elements to HTML5 media (source, tracks, etc)
- function _insertChildElements(type, attributes) {
- if (_is.string(attributes)) {
- _insertElement(type, plyr.media, { src: attributes });
- } else if (attributes.constructor === Array) {
- for (var i = attributes.length - 1; i >= 0; i--) {
- _insertElement(type, plyr.media, attributes[i]);
- }
- }
+ // Add class hook
+ toggleClass(player.elements.container, config.classes.captions.active, player.captions.enabled);
+
+ // Trigger an event
+ trigger(player.elements.container, player.captions.enabled ? 'captionsenabled' : 'captionsdisabled', true);
+
+ // Save captions state to localStorage
+ updateStorage({
+ captions: player.captions.enabled
+ });
}
// Insert controls
- function _injectControls() {
+ function injectControls() {
// Sprite
if (config.loadSprite) {
- var iconUrl = _getIconUrl();
+ var iconUrl = getIconUrl();
// Only load external sprite using AJAX
if (iconUrl.absolute) {
- _log('AJAX loading absolute SVG sprite' + (plyr.browser.isIE ? ' (due to IE)' : ''));
+ log('AJAX loading absolute SVG sprite' + (player.browser.isIE ? ' (due to IE)' : ''));
loadSprite(iconUrl.url, "sprite-plyr");
} else {
- _log('Sprite will be used as external resource directly');
+ log('Sprite will be used as external resource directly');
}
}
- // Make a copy of the html
- var html = config.html;
-
- // Insert custom video controls
- _log('Injecting custom controls');
-
- // If no controls are specified, create default
- if (!html) {
- html = _buildControls();
- }
-
- // Replace seek time instances
- html = _replaceAll(html, '{seektime}', config.seekTime);
-
- // Replace all id references with random numbers
- html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000)));
+ // Larger overlaid play button
+ if (inArray(config.controls, 'play-large')) {
+ player.elements.buttons.playLarge = createButton('play-large');
+ player.elements.container.appendChild(player.elements.buttons.playLarge);
+ }
+
+ // Create a unique ID
+ player.id = Math.floor(Math.random() * 10000);
+
+ // Create controls
+ var controls = createControls({
+ id: player.id,
+ seektime: config.seekTime,
+ speed: getSpeedDisplayValue(),
+ // TODO: Get current quality
+ quality: 'HD',
+ // TODO: Set language automatically based on UA?
+ captions: 'English',
+ // TODO: Get loop
+ loop: 'None'
+ });
// Controls container
var target;
// Inject to custom location
- if (_is.string(config.selectors.controls.container)) {
+ if (is.string(config.selectors.controls.container)) {
target = document.querySelector(config.selectors.controls.container);
}
// Inject into the container by default
- if (!_is.htmlElement(target)) {
- target = plyr.container
+ if (!is.htmlElement(target)) {
+ target = player.elements.container
}
// Inject controls HTML
- target.insertAdjacentHTML('beforeend', html);
+ // target.insertAdjacentHTML('beforeend', html);
+ target.appendChild(controls);
// Setup tooltips
if (config.tooltips.controls) {
- var labels = _getElements([config.selectors.controls.wrapper, ' ', config.selectors.labels, ' .', config.classes.hidden].join(''));
+ var labels = getElements([config.selectors.controls.wrapper, ' ', config.selectors.labels, ' .', config.classes.hidden].join(''));
for (var i = labels.length - 1; i >= 0; i--) {
var label = labels[i];
- _toggleClass(label, config.classes.hidden, false);
- _toggleClass(label, config.classes.tooltip, true);
+ toggleClass(label, config.classes.hidden, false);
+ toggleClass(label, config.classes.tooltip, true);
}
}
}
// Find the UI controls and store references
- function _findElements() {
+ // TODO: Restore when re-enabling custom HTML
+ /*function findElements() {
try {
- plyr.controls = _getElement(config.selectors.controls.wrapper);
+ player.elements.controls = getElement(config.selectors.controls.wrapper);
// Buttons
- plyr.buttons = {};
- plyr.buttons.seek = _getElement(config.selectors.buttons.seek);
- plyr.buttons.play = _getElements(config.selectors.buttons.play);
- plyr.buttons.pause = _getElement(config.selectors.buttons.pause);
- plyr.buttons.restart = _getElement(config.selectors.buttons.restart);
- plyr.buttons.rewind = _getElement(config.selectors.buttons.rewind);
- plyr.buttons.forward = _getElement(config.selectors.buttons.forward);
- plyr.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
-
- // Inputs
- plyr.buttons.mute = _getElement(config.selectors.buttons.mute);
- plyr.buttons.captions = _getElement(config.selectors.buttons.captions);
+ player.elements.buttons = {
+ play: getElements(config.selectors.buttons.play),
+ pause: getElement(config.selectors.buttons.pause),
+ restart: getElement(config.selectors.buttons.restart),
+ rewind: getElement(config.selectors.buttons.rewind),
+ forward: getElement(config.selectors.buttons.forward),
+ fullscreen: getElement(config.selectors.buttons.fullscreen),
+ settings: getElement(config.selectors.buttons.settings),
+ pip: getElement(config.selectors.buttons.pip),
+ //lang: getElement(config.selectors.buttons.captions_lang),
+ speed: getElement(config.selectors.buttons.speed),
+ loop: getElement(config.selectors.buttons.loop),
+ mute: getElement(config.selectors.buttons.mute),
+ captions: getElement(config.selectors.buttons.captions)
+ };
// Progress
- plyr.progress = {};
- plyr.progress.container = _getElement(config.selectors.progress.container);
+ player.elements.progress = getElement(config.selectors.progress);
- // Progress - Buffering
- plyr.progress.buffer = {};
- plyr.progress.buffer.bar = _getElement(config.selectors.progress.buffer);
- plyr.progress.buffer.text = plyr.progress.buffer.bar && plyr.progress.buffer.bar.getElementsByTagName('span')[0];
+ // Inputs
+ player.elements.inputs = {
+ seek: getElement(config.selectors.inputs.seek),
+ volume: getElement(config.selectors.inputs.volume)
+ };
- // Progress - Played
- plyr.progress.played = _getElement(config.selectors.progress.played);
+ // Display
+ player.elements.display = {
+ buffer: getElement(config.selectors.display.buffer),
+ played: getElement(config.selectors.display.played),
+ volume: getElement(config.selectors.display.volume),
+ duration: getElement(config.selectors.display.duration),
+ currentTime: getElement(config.selectors.display.currentTime),
+ };
// Seek tooltip
- plyr.progress.tooltip = plyr.progress.container && plyr.progress.container.querySelector('.' + config.classes.tooltip);
-
- // Volume
- plyr.volume = {};
- plyr.volume.input = _getElement(config.selectors.volume.input);
- plyr.volume.display = _getElement(config.selectors.volume.display);
-
- // Timing
- plyr.duration = _getElement(config.selectors.duration);
- plyr.currentTime = _getElement(config.selectors.currentTime);
- plyr.seekTime = _getElements(config.selectors.seekTime);
+ if (is.htmlElement(player.elements.progress)) {
+ player.elements.display.seekTooltip = player.elements.progress.querySelector('.' + config.classes.tooltip);
+ }
return true;
- }
- catch(e) {
- _warn('It looks like there is a problem with your controls HTML');
+ } catch (error) {
+ warn('It looks like there is a problem with your custom controls HTML', error);
// Restore native video controls
- _toggleNativeControls(true);
+ toggleNativeControls(true);
return false;
}
- }
+ }*/
// Toggle style hook
- function _toggleStyleHook() {
- _toggleClass(plyr.container, config.selectors.container.replace('.', ''), plyr.supported.full);
+ function toggleStyleHook() {
+ toggleClass(player.elements.container, config.selectors.container.replace('.', ''), player.supported.full);
}
// Toggle native controls
- function _toggleNativeControls(toggle) {
- if (toggle && _inArray(config.types.html5, plyr.type)) {
- plyr.media.setAttribute('controls', '');
+ function toggleNativeControls(toggle) {
+ if (toggle && inArray(config.types.html5, player.type)) {
+ player.elements.media.setAttribute('controls', '');
} else {
- plyr.media.removeAttribute('controls');
+ player.elements.media.removeAttribute('controls');
}
}
// Setup aria attribute for play and iframe title
- function _setTitle(iframe) {
+ function setTitle(iframe) {
// Find the current text
var label = config.i18n.play;
// If there's a media title set, use that for the label
- if (_is.string(config.title) && config.title.length) {
+ if (is.string(config.title) && config.title.length) {
label += ', ' + config.title;
// Set container label
- plyr.container.setAttribute('aria-label', config.title);
+ player.elements.container.setAttribute('aria-label', config.title);
}
// If there's a play button, set label
- if (plyr.supported.full && plyr.buttons.play) {
- for (var i = plyr.buttons.play.length - 1; i >= 0; i--) {
- plyr.buttons.play[i].setAttribute('aria-label', label);
+ if (player.supported.full) {
+ if (is.htmlElement(player.elements.buttons.play)) {
+ player.elements.buttons.play.setAttribute('aria-label', label);
+ }
+ if (is.htmlElement(player.elements.buttons.playLarge)) {
+ player.elements.buttons.playLarge.setAttribute('aria-label', label);
}
}
// Set iframe title
// https://github.com/Selz/plyr/issues/124
- if (_is.htmlElement(iframe)) {
+ if (is.htmlElement(iframe)) {
iframe.setAttribute('title', config.i18n.frameTitle.replace('{title}', config.title));
}
}
// Setup localStorage
- function _setupStorage() {
+ function setupStorage() {
var value = null;
- plyr.storage = {};
+ player.storage = {};
// Bail if we don't have localStorage support or it's disabled
- if (!_storage.supported || !config.storage.enabled) {
+ if (!support.storage || !config.storage.enabled) {
return;
}
@@ -1440,260 +2394,290 @@
return;
} else if (/^\d+(\.\d+)?$/.test(value)) {
// If value is a number, it's probably volume from an older
- // version of plyr. See: https://github.com/Selz/plyr/pull/313
+ // version of player. See: https://github.com/Selz/plyr/pull/313
// Update the key to be JSON
- _updateStorage({volume: parseFloat(value)});
+ updateStorage({
+ volume: parseFloat(value)
+ });
} else {
// Assume it's JSON from this or a later version of plyr
- plyr.storage = JSON.parse(value);
+ player.storage = JSON.parse(value);
}
}
// Save a value back to local storage
- function _updateStorage(value) {
+ function updateStorage(value) {
// Bail if we don't have localStorage support or it's disabled
- if (!_storage.supported || !config.storage.enabled) {
+ if (!support.storage || !config.storage.enabled) {
return;
}
// Update the working copy of the values
- _extend(plyr.storage, value);
+ extend(player.storage, value);
// Update storage
- window.localStorage.setItem(config.storage.key, JSON.stringify(plyr.storage));
+ window.localStorage.setItem(config.storage.key, JSON.stringify(player.storage));
}
// Setup media
- function _setupMedia() {
+ function setupMedia() {
// If there's no media, bail
- if (!plyr.media) {
- _warn('No media element found!');
+ if (!player.elements.media) {
+ warn('No media element found!');
return;
}
- if (plyr.supported.full) {
+ if (player.supported.full) {
// Add type class
- _toggleClass(plyr.container, config.classes.type.replace('{0}', plyr.type), true);
+ toggleClass(player.elements.container, config.classes.type.replace('{0}', player.type), true);
// Add video class for embeds
// This will require changes if audio embeds are added
- if (_inArray(config.types.embed, plyr.type)) {
- _toggleClass(plyr.container, config.classes.type.replace('{0}', 'video'), true);
+ if (inArray(config.types.embed, player.type)) {
+ toggleClass(player.elements.container, config.classes.type.replace('{0}', 'video'), true);
}
+ // Check for picture-in-picture support
+ toggleClass(player.elements.container, config.classes.pip.enabled, support.pip && player.type === 'video');
+
+ // Check for airplay support
+ toggleClass(player.elements.container, config.classes.airplay.enabled, support.airplay && inArray(config.types.html5, player.type));
+
// If there's no autoplay attribute, assume the video is stopped and add state class
- _toggleClass(plyr.container, config.classes.stopped, config.autoplay);
+ toggleClass(player.elements.container, config.classes.stopped, config.autoplay);
// Add iOS class
- _toggleClass(plyr.container, config.classes.isIos, plyr.browser.isIos);
+ toggleClass(player.elements.container, config.classes.isIos, player.browser.isIos);
// Add touch class
- _toggleClass(plyr.container, config.classes.isTouch, plyr.browser.isTouch);
+ toggleClass(player.elements.container, config.classes.isTouch, player.browser.isTouch);
// Inject the player wrapper
- if (plyr.type === 'video') {
+ if (player.type === 'video') {
// Create the wrapper div
- var wrapper = document.createElement('div');
+ var wrapper = createElement('div');
wrapper.setAttribute('class', config.classes.videoWrapper);
// Wrap the video in a container
- _wrap(plyr.media, wrapper);
+ wrap(player.elements.media, wrapper);
// Cache the container
- plyr.videoContainer = wrapper;
+ player.elements.wrapper = wrapper;
}
}
// Embeds
- if (_inArray(config.types.embed, plyr.type)) {
- _setupEmbed();
+ if (inArray(config.types.embed, player.type)) {
+ setupEmbed();
}
}
// Setup YouTube/Vimeo
- function _setupEmbed() {
- var container = document.createElement('div'),
- mediaId,
- id = plyr.type + '-' + Math.floor(Math.random() * (10000));
+ function setupEmbed() {
+ var container = createElement('div');
+ var mediaId;
+ var id = player.type + '-' + Math.floor(Math.random() * (10000));
// Parse IDs from URLs if supplied
- switch (plyr.type) {
+ switch (player.type) {
case 'youtube':
- mediaId = _parseYouTubeId(plyr.embedId);
+ mediaId = parseYouTubeId(player.embedId);
break;
default:
- mediaId = plyr.embedId;
+ mediaId = player.embedId;
}
// Remove old containers
- var containers = _getElements('[id^="' + plyr.type + '-"]');
+ var containers = getElements('[id^="' + player.type + '-"]');
for (var i = containers.length - 1; i >= 0; i--) {
- _remove(containers[i]);
+ remove(containers[i]);
}
// Add embed class for responsive
- _toggleClass(plyr.media, config.classes.videoWrapper, true);
- _toggleClass(plyr.media, config.classes.embedWrapper, true);
+ toggleClass(player.elements.media, config.classes.videoWrapper, true);
+ toggleClass(player.elements.media, config.classes.embedWrapper, true);
- if (plyr.type === 'youtube') {
+ if (player.type === 'youtube') {
// Create the YouTube container
- plyr.media.appendChild(container);
+ player.elements.media.appendChild(container);
// Set ID
container.setAttribute('id', id);
// Setup API
- if (_is.object(window.YT)) {
- _youTubeReady(mediaId, container);
+ if (is.object(window.YT)) {
+ youTubeReady(mediaId, container);
} else {
// Load the API
- _injectScript(config.urls.youtube.api);
+ injectScript(config.urls.youtube.api);
// Setup callback for the API
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue
- window.onYouTubeReadyCallbacks.push(function() { _youTubeReady(mediaId, container); });
+ window.onYouTubeReadyCallbacks.push(function() {
+ youTubeReady(mediaId, container);
+ });
// Set callback to process queue
- window.onYouTubeIframeAPIReady = function () {
- window.onYouTubeReadyCallbacks.forEach(function(callback) { callback(); });
+ window.onYouTubeIframeAPIReady = function() {
+ window.onYouTubeReadyCallbacks.forEach(function(callback) {
+ callback();
+ });
};
}
- } else if (plyr.type === 'vimeo') {
+ } else if (player.type === 'vimeo') {
// Vimeo needs an extra div to hide controls on desktop (which has full support)
- if (plyr.supported.full) {
- plyr.media.appendChild(container);
+ if (player.supported.full) {
+ player.elements.media.appendChild(container);
} else {
- container = plyr.media;
+ container = player.elements.media;
}
// Set ID
container.setAttribute('id', id);
// Load the API if not already
- if (!_is.object(window.Vimeo)) {
- _injectScript(config.urls.vimeo.api);
+ if (!is.object(window.Vimeo)) {
+ injectScript(config.urls.vimeo.api);
// Wait for fragaloop load
var vimeoTimer = window.setInterval(function() {
- if (_is.object(window.Vimeo)) {
+ if (is.object(window.Vimeo)) {
window.clearInterval(vimeoTimer);
- _vimeoReady(mediaId, container);
+ vimeoReady(mediaId, container);
}
}, 50);
} else {
- _vimeoReady(mediaId, container);
+ vimeoReady(mediaId, container);
}
- } else if (plyr.type === 'soundcloud') {
+ } else if (player.type === 'soundcloud') {
// TODO: Currently unsupported and undocumented
// Inject the iframe
- var soundCloud = document.createElement('iframe');
+ var soundCloud = createElement('iframe');
// Watch for iframe load
soundCloud.loaded = false;
- _on(soundCloud, 'load', function() { soundCloud.loaded = true; });
+ on(soundCloud, 'load', function() {
+ soundCloud.loaded = true;
+ });
- _setAttributes(soundCloud, {
- 'src': 'https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/' + mediaId,
- 'id': id
+ setAttributes(soundCloud, {
+ 'src': 'https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/' + mediaId,
+ 'id': id
});
container.appendChild(soundCloud);
- plyr.media.appendChild(container);
+ player.elements.media.appendChild(container);
// Load the API if not already
if (!window.SC) {
- _injectScript(config.urls.soundcloud.api);
+ injectScript(config.urls.soundcloud.api);
}
// Wait for SC load
var soundCloudTimer = window.setInterval(function() {
if (window.SC && soundCloud.loaded) {
window.clearInterval(soundCloudTimer);
- _soundcloudReady.call(soundCloud);
+ soundcloudReady.call(soundCloud);
}
}, 50);
}
}
// When embeds are ready
- function _embedReady() {
+ function embedReady() {
// Setup the UI and call ready if full support
- if (plyr.supported.full) {
- _setupInterface();
- _ready();
+ if (player.supported.full) {
+ setupInterface();
+ ready();
}
// Set title
- _setTitle(_getElement('iframe'));
+ setTitle(getElement('iframe'));
}
// Handle YouTube API ready
- function _youTubeReady(videoId, container) {
+ function youTubeReady(videoId, container) {
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
- plyr.embed = new window.YT.Player(container.id, {
+ player.embed = new window.YT.Player(container.id, {
videoId: videoId,
playerVars: {
- autoplay: (config.autoplay ? 1 : 0),
- controls: (plyr.supported.full ? 0 : 1),
- rel: 0,
- showinfo: 0,
+ autoplay: (config.autoplay ? 1 : 0),
+ controls: (player.supported.full ? 0 : 1),
+ rel: 0,
+ showinfo: 0,
iv_load_policy: 3,
cc_load_policy: (config.captions.defaultActive ? 1 : 0),
- cc_lang_pref: 'en',
- wmode: 'transparent',
+ cc_lang_pref: 'en',
+ wmode: 'transparent',
modestbranding: 1,
- disablekb: 1,
- origin: '*' // https://code.google.com/p/gdata-issues/issues/detail?id=5788#c45
+ disablekb: 1,
+ origin: '*' // https://code.google.com/p/gdata-issues/issues/detail?id=5788#c45
},
events: {
'onError': function(event) {
- _triggerEvent(plyr.container, 'error', true, {
- code: event.data,
- embed: event.target
+ trigger(player.elements.container, 'error', true, {
+ code: event.data,
+ embed: event.target
});
},
+ 'onPlaybackQualityChange': function(event) {
+ // Get the instance
+ var instance = event.target;
+
+ // Get current quality
+ var quality = instance.getPlaybackQuality();
+
+ // var set = player.setPlaybackQuality();
+ console.warn(quality);
+ },
'onReady': function(event) {
// Get the instance
var instance = event.target;
// Create a faux HTML5 API using the YouTube API
- plyr.media.play = function() {
+ player.elements.media.play = function() {
instance.playVideo();
- plyr.media.paused = false;
+ player.elements.media.paused = false;
};
- plyr.media.pause = function() {
+ player.elements.media.pause = function() {
instance.pauseVideo();
- plyr.media.paused = true;
+ player.elements.media.paused = true;
};
- plyr.media.stop = function() {
+ player.elements.media.stop = function() {
instance.stopVideo();
- plyr.media.paused = true;
+ player.elements.media.paused = true;
};
- plyr.media.duration = instance.getDuration();
- plyr.media.paused = true;
- plyr.media.currentTime = 0;
- plyr.media.muted = instance.isMuted();
+ player.elements.media.duration = instance.getDuration();
+ player.elements.media.paused = true;
+ player.elements.media.currentTime = 0;
+ player.elements.media.muted = instance.isMuted();
+
+ // Get available speeds
+ var speed = instance.getPlaybackRate();
+ var speedOptions = instance.getAvailablePlaybackRates();
+ //var set = instance.setPlaybackRate();
+ console.warn(speed, speedOptions);
// Set title
config.title = instance.getVideoData().title;
// Set the tabindex
- if (plyr.supported.full) {
- plyr.media.querySelector('iframe').setAttribute('tabindex', '-1');
+ if (player.supported.full) {
+ player.elements.media.querySelector('iframe').setAttribute('tabindex', '-1');
}
// Update UI
- _embedReady();
+ embedReady();
// Trigger timeupdate
- _triggerEvent(plyr.media, 'timeupdate');
+ trigger(player.elements.media, 'timeupdate');
// Trigger timeupdate
- _triggerEvent(plyr.media, 'durationchange');
+ trigger(player.elements.media, 'durationchange');
// Reset timer
window.clearInterval(timers.buffering);
@@ -1701,22 +2685,22 @@
// Setup buffering
timers.buffering = window.setInterval(function() {
// Get loaded % from YouTube
- plyr.media.buffered = instance.getVideoLoadedFraction();
+ player.elements.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress only when we actually buffer something
- if (plyr.media.lastBuffered === null || plyr.media.lastBuffered < plyr.media.buffered) {
- _triggerEvent(plyr.media, 'progress');
+ if (player.elements.media.lastBuffered === null || player.elements.media.lastBuffered < player.elements.media.buffered) {
+ trigger(player.elements.media, 'progress');
}
// Set last buffer point
- plyr.media.lastBuffered = plyr.media.buffered;
+ player.elements.media.lastBuffered = player.elements.media.buffered;
// Bail if we're at 100%
- if (plyr.media.buffered === 1) {
+ if (player.elements.media.buffered === 1) {
window.clearInterval(timers.buffering);
// Trigger event
- _triggerEvent(plyr.media, 'canplaythrough');
+ trigger(player.elements.media, 'canplaythrough');
}
}, 200);
},
@@ -1736,48 +2720,53 @@
// 5 Video cued
switch (event.data) {
case 0:
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'ended');
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'ended');
break;
case 1:
- plyr.media.paused = false;
+ player.elements.media.paused = false;
// If we were seeking, fire seeked event
- if (plyr.media.seeking) {
- _triggerEvent(plyr.media, 'seeked');
+ if (player.elements.media.seeking) {
+ trigger(player.elements.media, 'seeked');
}
- plyr.media.seeking = false;
- _triggerEvent(plyr.media, 'play');
- _triggerEvent(plyr.media, 'playing');
+ player.elements.media.seeking = false;
+ trigger(player.elements.media, 'play');
+ trigger(player.elements.media, 'playing');
// Poll to get playback progress
timers.playing = window.setInterval(function() {
// Set the current time
- plyr.media.currentTime = instance.getCurrentTime();
+ player.elements.media.currentTime = instance.getCurrentTime();
// Trigger timeupdate
- _triggerEvent(plyr.media, 'timeupdate');
+ trigger(player.elements.media, 'timeupdate');
}, 100);
// Check duration again due to YouTube bug
// https://github.com/Selz/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
- if (plyr.media.duration !== instance.getDuration()) {
- plyr.media.duration = instance.getDuration();
- _triggerEvent(plyr.media, 'durationchange');
+ if (player.elements.media.duration !== instance.getDuration()) {
+ player.elements.media.duration = instance.getDuration();
+ trigger(player.elements.media, 'durationchange');
}
+ // Get quality
+ var qualityOptions = instance.getAvailableQualityLevels();
+ var quality = instance.getPlaybackQuality();
+ setQualityMenu(qualityOptions, quality);
+
break;
case 2:
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'pause');
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'pause');
break;
}
- _triggerEvent(plyr.container, 'statechange', false, {
+ trigger(player.elements.container, 'statechange', false, {
code: event.data
});
}
@@ -1786,7 +2775,7 @@
}
// Vimeo ready
- function _vimeoReady(mediaId, container) {
+ function vimeoReady(mediaId, container) {
// Setup instance
// https://github.com/vimeo/player.js
plyr.embed = new window.Vimeo.Player(container, {
@@ -1799,222 +2788,347 @@
});
// Create a faux HTML5 API using the Vimeo API
- plyr.media.play = function() {
- plyr.embed.play();
- plyr.media.paused = false;
+ player.elements.media.play = function() {
+ player.embed.play();
+ player.elements.media.paused = false;
};
- plyr.media.pause = function() {
- plyr.embed.pause();
- plyr.media.paused = true;
+ player.elements.media.pause = function() {
+ player.embed.pause();
+ player.elements.media.paused = true;
};
- plyr.media.stop = function() {
- plyr.embed.stop();
- plyr.media.paused = true;
+ player.elements.media.stop = function() {
+ player.embed.stop();
+ player.elements.media.paused = true;
};
- plyr.media.paused = true;
- plyr.media.currentTime = 0;
+ player.elements.media.paused = true;
+ player.elements.media.currentTime = 0;
// Update UI
- _embedReady();
+ embedReady();
- plyr.embed.getCurrentTime().then(function(value) {
- plyr.media.currentTime = value;
+ player.embed.getCurrentTime().then(function(value) {
+ player.elements.media.currentTime = value;
// Trigger timeupdate
- _triggerEvent(plyr.media, 'timeupdate');
+ trigger(player.elements.media, 'timeupdate');
});
- plyr.embed.getDuration().then(function(value) {
- plyr.media.duration = value;
+ player.embed.getDuration().then(function(value) {
+ player.elements.media.duration = value;
// Trigger timeupdate
- _triggerEvent(plyr.media, 'durationchange');
+ trigger(player.elements.media, 'durationchange');
});
// TODO: Captions
/*if (config.captions.defaultActive) {
- plyr.embed.enableTextTrack('en');
+ player.embed.enableTextTrack('en');
}*/
- plyr.embed.on('loaded', function() {
+ player.embed.on('loaded', function() {
// Fix keyboard focus issues
// https://github.com/Selz/plyr/issues/317
- if (_is.htmlElement(plyr.embed.element) && plyr.supported.full) {
- plyr.embed.element.setAttribute('tabindex', '-1');
+ if (is.htmlElement(player.embed.element) && player.supported.full) {
+ player.embed.element.setAttribute('tabindex', '-1');
}
});
- plyr.embed.on('play', function() {
- plyr.media.paused = false;
- _triggerEvent(plyr.media, 'play');
- _triggerEvent(plyr.media, 'playing');
+ player.embed.on('play', function() {
+ player.elements.media.paused = false;
+ trigger(player.elements.media, 'play');
+ trigger(player.elements.media, 'playing');
});
- plyr.embed.on('pause', function() {
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'pause');
+ player.embed.on('pause', function() {
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'pause');
});
- plyr.embed.on('timeupdate', function(data) {
- plyr.media.seeking = false;
- plyr.media.currentTime = data.seconds;
- _triggerEvent(plyr.media, 'timeupdate');
+ player.embed.on('timeupdate', function(data) {
+ player.elements.media.seeking = false;
+ player.elements.media.currentTime = data.seconds;
+ trigger(player.elements.media, 'timeupdate');
});
- plyr.embed.on('progress', function(data) {
- plyr.media.buffered = data.percent;
- _triggerEvent(plyr.media, 'progress');
+ player.embed.on('progress', function(data) {
+ player.elements.media.buffered = data.percent;
+ trigger(player.elements.media, 'progress');
if (parseInt(data.percent) === 1) {
// Trigger event
- _triggerEvent(plyr.media, 'canplaythrough');
+ trigger(player.elements.media, 'canplaythrough');
}
});
- plyr.embed.on('seeked', function() {
- plyr.media.seeking = false;
- _triggerEvent(plyr.media, 'seeked');
- _triggerEvent(plyr.media, 'play');
+ player.embed.on('seeked', function() {
+ player.elements.media.seeking = false;
+ trigger(player.elements.media, 'seeked');
+ trigger(player.elements.media, 'play');
});
- plyr.embed.on('ended', function() {
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'ended');
+ player.embed.on('ended', function() {
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'ended');
});
}
// Soundcloud ready
- function _soundcloudReady() {
+ function soundcloudReady() {
/* jshint validthis: true */
- plyr.embed = window.SC.Widget(this);
+ player.embed = window.SC.Widget(this);
// Setup on ready
- plyr.embed.bind(window.SC.Widget.Events.READY, function() {
+ player.embed.bind(window.SC.Widget.Events.READY, function() {
// Create a faux HTML5 API using the Soundcloud API
- plyr.media.play = function() {
- plyr.embed.play();
- plyr.media.paused = false;
+ player.elements.media.play = function() {
+ player.embed.play();
+ player.elements.media.paused = false;
};
- plyr.media.pause = function() {
- plyr.embed.pause();
- plyr.media.paused = true;
+ player.elements.media.pause = function() {
+ player.embed.pause();
+ player.elements.media.paused = true;
};
- plyr.media.stop = function() {
- plyr.embed.seekTo(0);
- plyr.embed.pause();
- plyr.media.paused = true;
+ player.elements.media.stop = function() {
+ player.embed.seekTo(0);
+ player.embed.pause();
+ player.elements.media.paused = true;
};
- plyr.media.paused = true;
- plyr.media.currentTime = 0;
+ player.elements.media.paused = true;
+ player.elements.media.currentTime = 0;
- plyr.embed.getDuration(function(value) {
- plyr.media.duration = value/1000;
+ player.embed.getDuration(function(value) {
+ player.elements.media.duration = value / 1000;
// Update UI
- _embedReady();
+ embedReady();
});
- plyr.embed.getPosition(function(value) {
- plyr.media.currentTime = value;
+ player.embed.getPosition(function(value) {
+ player.elements.media.currentTime = value;
// Trigger timeupdate
- _triggerEvent(plyr.media, 'timeupdate');
+ trigger(player.elements.media, 'timeupdate');
});
- plyr.embed.bind(window.SC.Widget.Events.PLAY, function() {
- plyr.media.paused = false;
- _triggerEvent(plyr.media, 'play');
- _triggerEvent(plyr.media, 'playing');
+ player.embed.bind(window.SC.Widget.Events.PLAY, function() {
+ player.elements.media.paused = false;
+ trigger(player.elements.media, 'play');
+ trigger(player.elements.media, 'playing');
});
- plyr.embed.bind(window.SC.Widget.Events.PAUSE, function() {
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'pause');
+ player.embed.bind(window.SC.Widget.Events.PAUSE, function() {
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'pause');
});
- plyr.embed.bind(window.SC.Widget.Events.PLAY_PROGRESS, function(data) {
- plyr.media.seeking = false;
- plyr.media.currentTime = data.currentPosition/1000;
- _triggerEvent(plyr.media, 'timeupdate');
+ player.embed.bind(window.SC.Widget.Events.PLAY_PROGRESS, function(data) {
+ player.elements.media.seeking = false;
+ player.elements.media.currentTime = data.currentPosition / 1000;
+ trigger(player.elements.media, 'timeupdate');
});
- plyr.embed.bind(window.SC.Widget.Events.LOAD_PROGRESS, function(data) {
- plyr.media.buffered = data.loadProgress;
- _triggerEvent(plyr.media, 'progress');
+ player.embed.bind(window.SC.Widget.Events.LOAD_PROGRESS, function(data) {
+ player.elements.media.buffered = data.loadProgress;
+ trigger(player.elements.media, 'progress');
if (parseInt(data.loadProgress) === 1) {
// Trigger event
- _triggerEvent(plyr.media, 'canplaythrough');
+ trigger(player.elements.media, 'canplaythrough');
}
});
- plyr.embed.bind(window.SC.Widget.Events.FINISH, function() {
- plyr.media.paused = true;
- _triggerEvent(plyr.media, 'ended');
+ player.embed.bind(window.SC.Widget.Events.FINISH, function() {
+ player.elements.media.paused = true;
+ trigger(player.elements.media, 'ended');
});
});
}
// Play media
- function _play() {
- if ('play' in plyr.media) {
- plyr.media.play();
+ function play() {
+ if ('play' in player.elements.media) {
+ player.elements.media.play();
}
}
// Pause media
- function _pause() {
- if ('pause' in plyr.media) {
- plyr.media.pause();
+ function pause() {
+ if ('pause' in player.elements.media) {
+ player.elements.media.pause();
}
}
// Toggle playback
- function _togglePlay(toggle) {
+ function togglePlay(toggle) {
// True toggle
- if (!_is.boolean(toggle)) {
- toggle = plyr.media.paused;
+ if (!is.boolean(toggle)) {
+ toggle = player.elements.media.paused;
}
if (toggle) {
- _play();
+ play();
} else {
- _pause();
+ pause();
}
return toggle;
}
+ // Toggle loop
+ // TODO: Set the indicator on load as user may pass loop as config
+ function toggleLoop(type) {
+ // Set default to be a true toggle
+ if (!inArray(['start', 'end', 'all', 'none', 'toggle'], type)) {
+ type = 'toggle';
+ }
+
+ var currentTime = Number(player.elements.media.currentTime);
+
+ switch (type) {
+ case 'start':
+ if (config.loop.end && config.loop.end <= currentTime) {
+ config.loop.end = null;
+ }
+ config.loop.start = currentTime;
+ config.loop.indicator.start = player.elements.display.played.value;
+ break;
+
+ case 'end':
+ if (config.loop.start >= currentTime) {
+ return;
+ }
+ config.loop.end = currentTime;
+ config.loop.indicator.end = player.elements.display.played.value;
+ break;
+
+ case 'all':
+ config.loop.start = 0;
+ config.loop.end = player.elements.media.duration - 2;
+ config.loop.indicator.start = 0;
+ config.loop.indicator.end = 100;
+ break;
+
+ case 'toggle':
+ if (config.loop.active) {
+ config.loop.start = 0;
+ config.loop.end = null;
+ } else {
+ config.loop.start = 0;
+ config.loop.end = player.elements.media.duration - 2;
+ }
+ break;
+
+ default:
+ config.loop.start = 0;
+ config.loop.end = null;
+ break;
+ }
+
+ // Check if can loop
+ config.loop.active = is.number(config.loop.start) && is.number(config.loop.end);
+ var start = updateTimeDisplay(config.loop.start, getElement('[data-plyr-loop="start"]'));
+ var end = null;
+
+ if (is.number(config.loop.end)) {
+ // Find the <span> inside button
+ end = updateTimeDisplay(config.loop.end, document.querySelector('[data-loop__value="loopout"]'));
+ } else {
+ // Find the <span> inside button
+ //end = document.querySelector('[data-loop__value="loopout"]').innerHTML = '';
+ }
+
+ if (config.loop.active) {
+ // TODO: Improve the design of the loop indicator and put styling in CSS where it's meant to be
+ //getElement('[data-menu="loop"]').innerHTML = start + ' - ' + end;
+ //getElement(config.selectors.progress.looped).style.position = 'absolute';
+ //getElement(config.selectors.progress.looped).style.left = config.loopinPositionPercentage + '%';
+ //getElement(config.selectors.progress.looped).style.width = (config.loopoutPositionPercentage - config.loopinPositionPercentage) + '%';
+ //getElement(config.selectors.progress.looped).style.background = '#ffbb00';
+ //getElement(config.selectors.progress.looped).style.height = '3px';
+ //getElement(config.selectors.progress.looped).style.top = '3px';
+ //getElement(config.selectors.progress.looped).style['border-radius'] = '100px';
+ } else {
+ //getElement('[data-menu="loop"]').innerHTML = config.i18n.loopNone;
+ //getElement(config.selectors.progress.looped).style.width = '0px';
+ }
+ }
+
+ // Set playback speed
+ function setSpeed(speed) {
+ // Load speed from storage or default value
+ if (is.undefined(speed)) {
+ speed = player.storage.speed || config.defaultSpeed;
+ }
+
+ if (!is.array(config.speeds)) {
+ warn('Invalid speeds format');
+ return;
+ }
+
+ if (!is.number(speed)) {
+ var index = config.speeds.indexOf(config.currentSpeed);
+
+ if (index !== -1) {
+ var nextIndex = index + 1;
+ if (nextIndex >= config.speeds.length) {
+ nextIndex = 0;
+ }
+ speed = config.speeds[nextIndex];
+ } else {
+ speed = config.defaultSpeed;
+ }
+ }
+
+ // Store current speed
+ config.currentSpeed = speed;
+
+ // Set HTML5 speed
+ player.elements.media.playbackRate = speed;
+
+ // Save speed to localStorage
+ updateStorage({
+ speed: speed
+ });
+
+ // Update current value of menu
+ // document.querySelector('[data-menu="speed"]').innerHTML = getSpeedDisplayValue();
+ }
+
+ // Get the current speed value
+ function getSpeedDisplayValue() {
+ return config.currentSpeed.toFixed(1).toString().replace('.0', '') + '&times;'
+ }
+
// Rewind
- function _rewind(seekTime) {
+ function rewind(seekTime) {
// Use default if needed
- if (!_is.number(seekTime)) {
+ if (!is.number(seekTime)) {
seekTime = config.seekTime;
}
- _seek(plyr.media.currentTime - seekTime);
+ seek(player.elements.media.currentTime - seekTime);
}
// Fast forward
- function _forward(seekTime) {
+ function forward(seekTime) {
// Use default if needed
- if (!_is.number(seekTime)) {
+ if (!is.number(seekTime)) {
seekTime = config.seekTime;
}
- _seek(plyr.media.currentTime + seekTime);
+ seek(player.elements.media.currentTime + seekTime);
}
// Seek to time
// The input parameter can be an event or a number
- function _seek(input) {
- var targetTime = 0,
- paused = plyr.media.paused,
- duration = _getDuration();
+ function seek(input) {
+ var targetTime = 0;
+ var paused = player.elements.media.paused;
+ var duration = getDuration();
- if (_is.number(input)) {
+ if (is.number(input)) {
targetTime = input;
- } else if (_is.object(input) && _inArray(['input', 'change'], input.type)) {
+ } else if (is.event(input) && inArray(['input', 'change'], input.type)) {
// It's the seek slider
// Seek to the selected time
targetTime = ((input.target.value / input.target.max) * duration);
@@ -2028,64 +3142,63 @@
}
// Update seek range and progress
- _updateSeekDisplay(targetTime);
+ updateSeekDisplay(targetTime);
// Set the current time
// Try/catch incase the media isn't set and we're calling seek() from source() and IE moans
try {
- plyr.media.currentTime = targetTime.toFixed(4);
- }
- catch(e) {}
+ player.elements.media.currentTime = targetTime.toFixed(4);
+ } catch (e) {}
// Embeds
- if (_inArray(config.types.embed, plyr.type)) {
- switch(plyr.type) {
+ if (inArray(config.types.embed, player.type)) {
+ switch (player.type) {
case 'youtube':
- plyr.embed.seekTo(targetTime);
+ player.embed.seekTo(targetTime);
break;
case 'vimeo':
// Round to nearest second for vimeo
- plyr.embed.setCurrentTime(targetTime.toFixed(0));
+ player.embed.setCurrentTime(targetTime.toFixed(0));
break;
case 'soundcloud':
- plyr.embed.seekTo(targetTime * 1000);
+ player.embed.seekTo(targetTime * 1000);
break;
}
if (paused) {
- _pause();
+ pause();
}
// Trigger timeupdate
- _triggerEvent(plyr.media, 'timeupdate');
+ trigger(player.elements.media, 'timeupdate');
// Set seeking flag
- plyr.media.seeking = true;
+ player.elements.media.seeking = true;
// Trigger seeking
- _triggerEvent(plyr.media, 'seeking');
+ trigger(player.elements.media, 'seeking');
}
// Logging
- _log('Seeking to ' + plyr.media.currentTime + ' seconds');
+ log('Seeking to ' + player.elements.media.currentTime + ' seconds');
// Special handling for 'manual' captions
- _seekManualCaptions(targetTime);
+ seekManualCaptions(targetTime);
}
// Get the duration (or custom if set)
- function _getDuration() {
+ function getDuration() {
// It should be a number, but parse it just incase
- var duration = parseInt(config.duration),
+ var duration = parseInt(config.duration);
// True duration
- mediaDuration = 0;
+ var mediaDuration = 0;
// Only if duration available
- if (plyr.media.duration !== null && !isNaN(plyr.media.duration)) {
- mediaDuration = plyr.media.duration;
+ if (player.elements.media.duration !== null && !isNaN(player.elements.media.duration)) {
+ mediaDuration = player.elements.media.duration;
}
// If custom duration is funky, use regular duration
@@ -2093,16 +3206,16 @@
}
// Check playing state
- function _checkPlaying() {
- _toggleClass(plyr.container, config.classes.playing, !plyr.media.paused);
+ function checkPlaying() {
+ toggleClass(player.elements.container, config.classes.playing, !player.elements.media.paused);
- _toggleClass(plyr.container, config.classes.stopped, plyr.media.paused);
+ toggleClass(player.elements.container, config.classes.stopped, player.elements.media.paused);
- _toggleControls(plyr.media.paused);
+ toggleControls(player.elements.media.paused);
}
// Save scroll position
- function _saveScrollPosition() {
+ function saveScrollPosition() {
scroll = {
x: window.pageXOffset || 0,
y: window.pageYOffset || 0
@@ -2110,110 +3223,175 @@
}
// Restore scroll position
- function _restoreScrollPosition() {
+ function restoreScrollPosition() {
window.scrollTo(scroll.x, scroll.y);
}
// Toggle fullscreen
- function _toggleFullscreen(event) {
+ function toggleFullscreen(event) {
// Check for native support
- var nativeSupport = fullscreen.supportsFullScreen;
+ var nativeSupport = support.fullscreen;
if (nativeSupport) {
// If it's a fullscreen change event, update the UI
- if (event && event.type === fullscreen.fullScreenEventName) {
- plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
+ if (event && event.type === fullscreen.eventType) {
+ player.fullscreen.active = fullscreen.isFullScreen(player.elements.container);
} else {
// Else it's a user request to enter or exit
- if (!fullscreen.isFullScreen(plyr.container)) {
+ if (!fullscreen.isFullScreen(player.elements.container)) {
// Save scroll position
- _saveScrollPosition();
+ saveScrollPosition();
// Request full screen
- fullscreen.requestFullScreen(plyr.container);
+ fullscreen.requestFullScreen(player.elements.container);
} else {
// Bail from fullscreen
fullscreen.cancelFullScreen();
}
// Check if we're actually full screen (it could fail)
- plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
+ player.fullscreen.active = fullscreen.isFullScreen(player.elements.container);
return;
}
} else {
// Otherwise, it's a simple toggle
- plyr.isFullscreen = !plyr.isFullscreen;
+ player.fullscreen.active = !player.fullscreen.active;
// Bind/unbind escape key
- document.body.style.overflow = plyr.isFullscreen ? 'hidden' : '';
+ document.body.style.overflow = player.fullscreen.active ? 'hidden' : '';
}
// Set class hook
- _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
+ toggleClass(player.elements.container, config.classes.fullscreen.active, player.fullscreen.active);
// Trap focus
- _focusTrap(plyr.isFullscreen);
+ focusTrap(player.fullscreen.active);
// Set button state
- if (plyr.buttons && plyr.buttons.fullscreen) {
- _toggleState(plyr.buttons.fullscreen, plyr.isFullscreen);
+ if (player.elements.buttons && player.elements.buttons.fullscreen) {
+ toggleState(player.elements.buttons.fullscreen, player.fullscreen.active);
}
// Trigger an event
- _triggerEvent(plyr.container, plyr.isFullscreen ? 'enterfullscreen' : 'exitfullscreen', true);
+ trigger(player.elements.container, player.fullscreen.active ? 'enterfullscreen' : 'exitfullscreen', true);
// Restore scroll position
- if (!plyr.isFullscreen && nativeSupport) {
- _restoreScrollPosition();
+ if (!player.fullscreen.active && nativeSupport) {
+ restoreScrollPosition();
+ }
+ }
+
+ // Toggle Menu
+ function toggleMenu(event) {
+ var menu = player.elements.settings.menu.parentNode;
+ var toggle = event.target;
+ var target = document.getElementById(toggle.getAttribute('aria-controls'));
+ var show = (toggle.getAttribute('aria-expanded') === 'false');
+
+ // Nothing to show, bail
+ if (!is.htmlElement(target)) {
+ return;
+ }
+
+ // Are we targetting a tab?
+ var isTab = target.getAttribute('role') === 'tabpanel';
+ var targetWidth;
+ var targetHeight;
+ var container;
+
+ // Hide all other tabs
+ if (isTab) {
+ // Get other tabs
+ var current = menu.querySelector('[role="tabpanel"][aria-hidden="false"]');
+ container = current.parentNode;
+
+ [].forEach.call(menu.querySelectorAll('[aria-controls="' + current.getAttribute('id') + '"]'), function(toggle) {
+ toggle.setAttribute('aria-expanded', false);
+ });
+
+ container.style.width = current.scrollWidth + 'px';
+ container.style.height = current.scrollHeight + 'px';
+
+ current.setAttribute('aria-hidden', true);
+ current.setAttribute('tabindex', -1);
+
+ // Get the natural element size
+ var clone = target.cloneNode(true);
+ clone.style.position = "absolute";
+ clone.style.opacity = 0;
+ clone.setAttribute('aria-hidden', false);
+ container.appendChild(clone);
+ targetWidth = clone.scrollWidth;
+ targetHeight = clone.scrollHeight;
+ remove(clone);
+ }
+
+ target.setAttribute('aria-hidden', !show);
+ toggle.setAttribute('aria-expanded', show);
+ target.setAttribute('tabindex', 0);
+
+ if (isTab) {
+ container.style.width = targetWidth + 'px';
+ container.style.height = targetHeight + 'px';
+
+ window.setTimeout(function() {
+ container.style.width = '';
+ container.style.height = '';
+ }, 300);
}
}
// Mute
- function _toggleMute(muted) {
+ function toggleMute(muted) {
// If the method is called without parameter, toggle based on current value
- if (!_is.boolean(muted)) {
- muted = !plyr.media.muted;
+ if (!is.boolean(muted)) {
+ muted = !player.elements.media.muted;
}
// Set button state
- _toggleState(plyr.buttons.mute, muted);
+ toggleState(player.elements.buttons.mute, muted);
// Set mute on the player
- plyr.media.muted = muted;
+ player.elements.media.muted = muted;
// If volume is 0 after unmuting, set to default
- if (plyr.media.volume === 0) {
- _setVolume(config.volume);
+ if (player.elements.media.volume === 0) {
+ setVolume(config.volume);
}
// Embeds
- if (_inArray(config.types.embed, plyr.type)) {
+ if (inArray(config.types.embed, player.type)) {
// YouTube
- switch(plyr.type) {
+ switch (player.type) {
case 'youtube':
- plyr.embed[plyr.media.muted ? 'mute' : 'unMute']();
+ player.embed[player.elements.media.muted ? 'mute' : 'unMute']();
break;
case 'vimeo':
case 'soundcloud':
- plyr.embed.setVolume(plyr.media.muted ? 0 : parseFloat(config.volume / config.volumeMax));
+ player.embed.setVolume(player.elements.media.muted ? 0 : parseFloat(config.volume / 10));
break;
}
// Trigger volumechange for embeds
- _triggerEvent(plyr.media, 'volumechange');
+ trigger(player.elements.media, 'volumechange');
}
}
// Set volume
- function _setVolume(volume) {
- var max = config.volumeMax,
- min = config.volumeMin;
+ function setVolume(volume) {
+ var max = 10;
+ var min = 0;
+
+ // If volume is event, get from input
+ if (is.event(volume)) {
+ volume = volume.target.value;
+ }
// Load volume from storage if no value specified
- if (_is.undefined(volume)) {
- volume = plyr.storage.volume;
+ if (is.undefined(volume)) {
+ volume = player.storage.volume;
}
// Use config if all else fails
@@ -2231,117 +3409,91 @@
}
// Set the player volume
- plyr.media.volume = parseFloat(volume / max);
+ player.elements.media.volume = parseFloat(volume / max);
// Set the display
- if (plyr.volume.display) {
- plyr.volume.display.value = volume;
+ if (player.elements.display.volume) {
+ player.elements.display.volume.value = volume;
}
// Embeds
- if (_inArray(config.types.embed, plyr.type)) {
- switch(plyr.type) {
+ if (inArray(config.types.embed, player.type)) {
+ switch (player.type) {
case 'youtube':
- plyr.embed.setVolume(plyr.media.volume * 100);
+ player.embed.setVolume(player.elements.media.volume * 100);
break;
case 'vimeo':
case 'soundcloud':
- plyr.embed.setVolume(plyr.media.volume);
+ player.embed.setVolume(player.elements.media.volume);
break;
}
// Trigger volumechange for embeds
- _triggerEvent(plyr.media, 'volumechange');
+ trigger(player.elements.media, 'volumechange');
}
// Toggle muted state
if (volume === 0) {
- plyr.media.muted = true;
- } else if (plyr.media.muted && volume > 0) {
- _toggleMute();
+ player.elements.media.muted = true;
+ } else if (player.elements.media.muted && volume > 0) {
+ toggleMute();
}
}
// Increase volume
- function _increaseVolume(step) {
- var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
+ function increaseVolume(step) {
+ var volume = player.elements.media.muted ? 0 : (player.elements.media.volume * 10);
- if (!_is.number(step)) {
- step = config.volumeStep;
+ if (!is.number(step)) {
+ step = 1;
}
- _setVolume(volume + step);
+ setVolume(volume + step);
}
// Decrease volume
- function _decreaseVolume(step) {
- var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
+ function decreaseVolume(step) {
+ var volume = player.elements.media.muted ? 0 : (player.elements.media.volume * 10);
- if (!_is.number(step)) {
- step = config.volumeStep;
+ if (!is.number(step)) {
+ step = 1;
}
- _setVolume(volume - step);
+ setVolume(volume - step);
}
// Update volume UI and storage
- function _updateVolume() {
+ function updateVolume() {
// Get the current volume
- var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
+ var volume = player.elements.media.muted ? 0 : (player.elements.media.volume * 10);
// Update the <input type="range"> if present
- if (plyr.supported.full) {
- if (plyr.volume.input) {
- plyr.volume.input.value = volume;
+ if (player.supported.full) {
+ if (player.elements.inputs.volume) {
+ player.elements.inputs.volume.value = volume;
}
- if (plyr.volume.display) {
- plyr.volume.display.value = volume;
+ if (player.elements.display.volume) {
+ player.elements.display.volume.value = volume;
}
}
// Update the volume in storage
- _updateStorage({volume: volume});
+ updateStorage({
+ volume: volume
+ });
// Toggle class if muted
- _toggleClass(plyr.container, config.classes.muted, (volume === 0));
+ toggleClass(player.elements.container, config.classes.muted, (volume === 0));
// Update checkbox for mute state
- if (plyr.supported.full && plyr.buttons.mute) {
- _toggleState(plyr.buttons.mute, (volume === 0));
- }
- }
-
- // Toggle captions
- function _toggleCaptions(show) {
- // If there's no full support, or there's no caption toggle
- if (!plyr.supported.full || !plyr.buttons.captions) {
- return;
+ if (player.supported.full && player.elements.buttons.mute) {
+ toggleState(player.elements.buttons.mute, (volume === 0));
}
-
- // If the method is called without parameter, toggle based on current value
- if (!_is.boolean(show)) {
- show = (plyr.container.className.indexOf(config.classes.captions.active) === -1);
- }
-
- // Set global
- plyr.captionsEnabled = show;
-
- // Toggle state
- _toggleState(plyr.buttons.captions, plyr.captionsEnabled);
-
- // Add class hook
- _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
-
- // Trigger an event
- _triggerEvent(plyr.container, plyr.captionsEnabled ? 'captionsenabled' : 'captionsdisabled', true);
-
- // Save captions state to localStorage
- _updateStorage({captionsEnabled: plyr.captionsEnabled});
}
// Check if media is loading
- function _checkLoading(event) {
+ function checkLoading(event) {
var loading = (event.type === 'waiting');
// Clear timer
@@ -2350,52 +3502,52 @@
// Timer to prevent flicker when seeking
timers.loading = setTimeout(function() {
// Toggle container class hook
- _toggleClass(plyr.container, config.classes.loading, loading);
+ toggleClass(player.elements.container, config.classes.loading, loading);
// Show controls if loading, hide if done
- _toggleControls(loading);
+ toggleControls(loading);
}, (loading ? 250 : 0));
}
// Update <progress> elements
- function _updateProgress(event) {
- if (!plyr.supported.full) {
+ function updateProgress(event) {
+ if (!player.supported.full) {
return;
}
- var progress = plyr.progress.played,
- value = 0,
- duration = _getDuration();
+ var progress = player.elements.display.played,
+ value = 0,
+ duration = getDuration();
if (event) {
switch (event.type) {
// Video playing
case 'timeupdate':
case 'seeking':
- if (plyr.controls.pressed) {
+ if (player.elements.controls.pressed) {
return;
}
- value = _getPercentage(plyr.media.currentTime, duration);
+ value = getPercentage(player.elements.media.currentTime, duration);
// Set seek range value only if it's a 'natural' time event
- if (event.type === 'timeupdate' && plyr.buttons.seek) {
- plyr.buttons.seek.value = value;
+ if (event.type === 'timeupdate' && player.elements.inputs.seek) {
+ player.elements.inputs.seek.value = value;
}
break;
- // Check buffer status
+ // Check buffer status
case 'playing':
case 'progress':
- progress = plyr.progress.buffer;
- value = (function() {
- var buffered = plyr.media.buffered;
+ progress = player.elements.display.buffer;
+ value = (function() {
+ var buffered = player.elements.media.buffered;
if (buffered && buffered.length) {
// HTML5
- return _getPercentage(buffered.end(0), duration);
- } else if (_is.number(buffered)) {
+ return getPercentage(buffered.end(0), duration);
+ } else if (is.number(buffered)) {
// YouTube returns between 0 and 1
return (buffered * 100);
}
@@ -2407,45 +3559,46 @@
}
}
- // Set values
- _setProgress(progress, value);
+ if (is.number(config.loop.start) && is.number(config.loop.end) && player.elements.media.currentTime >= config.loop.end) {
+ seek(config.loop.start);
+ }
+
+ setProgress(progress, value);
}
// Set <progress> value
- function _setProgress(progress, value) {
- if (!plyr.supported.full) {
+ function setProgress(progress, value) {
+ if (!player.supported.full) {
return;
}
// Default to 0
- if (_is.undefined(value)) {
+ if (is.undefined(value)) {
value = 0;
}
// Default to buffer or bail
- if (_is.undefined(progress)) {
- if (plyr.progress && plyr.progress.buffer) {
- progress = plyr.progress.buffer;
+ if (is.undefined(progress)) {
+ if (is.htmlElement(player.elements.display.buffer)) {
+ progress = player.elements.display.buffer;
} else {
return;
}
}
- // One progress element passed
- if (_is.htmlElement(progress)) {
+ // Update value and label
+ if (is.htmlElement(progress)) {
progress.value = value;
- } else if (progress) {
- // Object of progress + text element
- if (progress.bar) {
- progress.bar.value = value;
- }
- if (progress.text) {
- progress.text.innerHTML = value;
+
+ // Update text label inside
+ var label = progress.getElementsByTagName('span')[0];
+ if (is.htmlElement(label)) {
+ label.childNodes[0].nodeValue = value;
}
}
}
// Update the displayed time
- function _updateTimeDisplay(time, element) {
+ function updateTimeDisplay(time, element) {
// Bail if there's no duration display
if (!element) {
return;
@@ -2456,102 +3609,108 @@
time = 0;
}
- plyr.secs = parseInt(time % 60);
- plyr.mins = parseInt((time / 60) % 60);
- plyr.hours = parseInt(((time / 60) / 60) % 60);
+ var secs = parseInt(time % 60);
+ var mins = parseInt((time / 60) % 60);
+ var hours = parseInt(((time / 60) / 60) % 60);
// Do we need to display hours?
- var displayHours = (parseInt(((_getDuration() / 60) / 60) % 60) > 0);
+ var displayHours = (parseInt(((getDuration() / 60) / 60) % 60) > 0);
// Ensure it's two digits. For example, 03 rather than 3.
- plyr.secs = ('0' + plyr.secs).slice(-2);
- plyr.mins = ('0' + plyr.mins).slice(-2);
+ secs = ('0' + secs).slice(-2);
+ mins = ('0' + mins).slice(-2);
+
+ // Generate display
+ var display = (displayHours ? hours + ':' : '') + mins + ':' + secs;
// Render
- element.innerHTML = (displayHours ? plyr.hours + ':' : '') + plyr.mins + ':' + plyr.secs;
+ element.textContent = display;
+
+ // Return for looping
+ return display;
}
// Show the duration on metadataloaded
- function _displayDuration() {
- if (!plyr.supported.full) {
+ function displayDuration() {
+ if (!player.supported.full) {
return;
}
// Determine duration
- var duration = _getDuration() || 0;
+ var duration = getDuration() || 0;
// If there's only one time display, display duration there
- if (!plyr.duration && config.displayDuration && plyr.media.paused) {
- _updateTimeDisplay(duration, plyr.currentTime);
+ if (!player.elements.display.duration && config.displayDuration && player.elements.media.paused) {
+ updateTimeDisplay(duration, player.elements.display.currentTime);
}
// If there's a duration element, update content
- if (plyr.duration) {
- _updateTimeDisplay(duration, plyr.duration);
+ if (player.elements.display.duration) {
+ updateTimeDisplay(duration, player.elements.display.duration);
}
// Update the tooltip (if visible)
- _updateSeekTooltip();
+ updateSeekTooltip();
}
// Handle time change event
- function _timeUpdate(event) {
+ function timeUpdate(event) {
// Duration
- _updateTimeDisplay(plyr.media.currentTime, plyr.currentTime);
+ updateTimeDisplay(player.elements.media.currentTime, player.elements.display.currentTime);
// Ignore updates while seeking
- if (event && event.type === 'timeupdate' && plyr.media.seeking) {
+ if (event && event.type === 'timeupdate' && player.elements.media.seeking) {
return;
}
// Playing progress
- _updateProgress(event);
+ updateProgress(event);
}
// Update seek range and progress
- function _updateSeekDisplay(time) {
+ function updateSeekDisplay(time) {
// Default to 0
- if (!_is.number(time)) {
+ if (!is.number(time)) {
time = 0;
}
- var duration = _getDuration(),
- value = _getPercentage(time, duration);
+ var duration = getDuration(),
+ value = getPercentage(time, duration);
// Update progress
- if (plyr.progress && plyr.progress.played) {
- plyr.progress.played.value = value;
+ if (player.elements.progress && player.elements.display.played) {
+ player.elements.display.played.value = value;
}
// Update seek range input
- if (plyr.buttons && plyr.buttons.seek) {
- plyr.buttons.seek.value = value;
+ if (player.elements.buttons && player.elements.inputs.seek) {
+ player.elements.inputs.seek.value = value;
}
}
// Update hover tooltip for seeking
- function _updateSeekTooltip(event) {
- var duration = _getDuration();
+ function updateSeekTooltip(event) {
+ var duration = getDuration();
// Bail if setting not true
- if (!config.tooltips.seek || !plyr.progress.container || duration === 0) {
+ if (!config.tooltips.seek || !is.htmlElement(player.elements.inputs.seek) || !is.htmlElement(player.elements.display.seekTooltip) || duration === 0) {
return;
}
// Calculate percentage
- var clientRect = plyr.progress.container.getBoundingClientRect(),
- percent = 0,
- visible = config.classes.tooltip + '--visible';
+ var clientRect = player.elements.inputs.seek.getBoundingClientRect();
+ var percent = 0;
+ var visible = config.classes.tooltip + '--visible';
// Determine percentage, if already visible
- if (!event) {
- if (_hasClass(plyr.progress.tooltip, visible)) {
- percent = plyr.progress.tooltip.style.left.replace('%', '');
+ if (is.event(event)) {
+ percent = ((100 / clientRect.width) * (event.pageX - clientRect.left));
+ } else {
+ if (hasClass(player.elements.display.seekTooltip, visible)) {
+ percent = player.elements.display.seekTooltip.style.left.replace('%', '');
} else {
return;
}
- } else {
- percent = ((100 / clientRect.width) * (event.pageX - clientRect.left));
}
// Set bounds
@@ -2562,41 +3721,41 @@
}
// Display the time a click would seek to
- _updateTimeDisplay(((duration / 100) * percent), plyr.progress.tooltip);
+ updateTimeDisplay(((duration / 100) * percent), player.elements.display.seekTooltip);
// Set position
- plyr.progress.tooltip.style.left = percent + "%";
+ player.elements.display.seekTooltip.style.left = percent + "%";
// Show/hide the tooltip
// If the event is a moues in/out and percentage is inside bounds
- if (event && _inArray(['mouseenter', 'mouseleave'], event.type)) {
- _toggleClass(plyr.progress.tooltip, visible, (event.type === 'mouseenter'));
+ if (is.event(event) && inArray(['mouseenter', 'mouseleave'], event.type)) {
+ toggleClass(player.elements.display.seekTooltip, visible, (event.type === 'mouseenter'));
}
}
// Show the player controls in fullscreen mode
- function _toggleControls(toggle) {
+ function toggleControls(toggle) {
// Don't hide if config says not to, it's audio, or not ready or loading
- if (!config.hideControls || plyr.type === 'audio') {
+ if (!config.hideControls || player.type === 'audio') {
return;
}
- var delay = 0,
- isEnterFullscreen = false,
- show = toggle,
- loading = _hasClass(plyr.container, config.classes.loading);
+ var delay = 0;
+ var isEnterFullscreen = false;
+ var show = toggle;
+ var loading = hasClass(player.elements.container, config.classes.loading);
// Default to false if no boolean
- if (!_is.boolean(toggle)) {
+ if (!is.boolean(toggle)) {
if (toggle && toggle.type) {
// Is the enter fullscreen event
isEnterFullscreen = (toggle.type === 'enterfullscreen');
// Whether to show controls
- show = _inArray(['mousemove', 'touchstart', 'mouseenter', 'focus'], toggle.type);
+ show = inArray(['mousemove', 'touchstart', 'mouseenter', 'focus'], toggle.type);
// Delay hiding on move events
- if (_inArray(['mousemove', 'touchmove'], toggle.type)) {
+ if (inArray(['mousemove', 'touchmove'], toggle.type)) {
delay = 2000;
}
@@ -2605,7 +3764,7 @@
delay = 3000;
}
} else {
- show = _hasClass(plyr.container, config.classes.hideControls);
+ show = hasClass(player.elements.container, config.classes.hideControls);
}
}
@@ -2613,63 +3772,63 @@
window.clearTimeout(timers.hover);
// If the mouse is not over the controls, set a timeout to hide them
- if (show || plyr.media.paused || loading) {
- _toggleClass(plyr.container, config.classes.hideControls, false);
+ if (show || player.elements.media.paused || loading) {
+ toggleClass(player.elements.container, config.classes.hideControls, false);
// Always show controls when paused or if touch
- if (plyr.media.paused || loading) {
+ if (player.elements.media.paused || loading) {
return;
}
// Delay for hiding on touch
- if (plyr.browser.isTouch) {
+ if (player.browser.isTouch) {
delay = 3000;
}
}
// If toggle is false or if we're playing (regardless of toggle),
// then set the timer to hide the controls
- if (!show || !plyr.media.paused) {
+ if (!show || !player.elements.media.paused) {
timers.hover = window.setTimeout(function() {
// If the mouse is over the controls (and not entering fullscreen), bail
- if ((plyr.controls.pressed || plyr.controls.hover) && !isEnterFullscreen) {
+ if ((player.elements.controls.pressed || player.elements.controls.hover) && !isEnterFullscreen) {
return;
}
- _toggleClass(plyr.container, config.classes.hideControls, true);
+ toggleClass(player.elements.container, config.classes.hideControls, true);
}, delay);
}
}
// Add common function to retrieve media source
- function _source(source) {
+ function source(source) {
// If not null or undefined, parse it
- if (!_is.undefined(source)) {
- _updateSource(source);
+ if (!is.undefined(source)) {
+ updateSource(source);
return;
}
// Return the current source
var url;
- switch(plyr.type) {
+ switch (player.type) {
case 'youtube':
- url = plyr.embed.getVideoUrl();
+ url = player.embed.getVideoUrl();
break;
case 'vimeo':
- plyr.embed.getVideoUrl.then(function (value) {
+ player.embed.getVideoUrl.then(function(value) {
url = value;
});
break;
case 'soundcloud':
- plyr.embed.getCurrentSound(function(object) {
+ player.embed.getCurrentSound(function(object) {
url = object.permalink_url;
});
break;
default:
- url = plyr.media.currentSrc;
+ url = player.elements.media.currentSrc;
break;
}
@@ -2678,202 +3837,182 @@
// Update source
// Sources are not checked for support so be careful
- function _updateSource(source) {
- if (!_is.object(source) || !('sources' in source) || !source.sources.length) {
- _warn('Invalid source format');
+ function updateSource(source) {
+ if (!is.object(source) || !('sources' in source) || !source.sources.length) {
+ warn('Invalid source format');
return;
}
// Remove ready class hook
- _toggleClass(plyr.container, config.classes.ready, false);
+ toggleClass(player.elements.container, config.classes.ready, false);
// Pause playback
- _pause();
+ pause();
// Update seek range and progress
- _updateSeekDisplay();
+ updateSeekDisplay();
// Reset buffer progress
- _setProgress();
+ setProgress();
// Cancel current network requests
- _cancelRequests();
+ cancelRequests();
// Setup new source
function setup() {
// Remove embed object
- plyr.embed = null;
+ player.embed = null;
// Remove the old media
- _remove(plyr.media);
+ remove(player.elements.media);
// Remove video container
- if (plyr.type === 'video' && plyr.videoContainer) {
- _remove(plyr.videoContainer);
+ if (player.type === 'video' && player.elements.wrapper) {
+ remove(player.elements.wrapper);
}
// Reset class name
- if (plyr.container) {
- plyr.container.removeAttribute('class');
+ if (player.elements.container) {
+ player.elements.container.removeAttribute('class');
}
// Set the type
if ('type' in source) {
- plyr.type = source.type;
+ player.type = source.type;
// Get child type for video (it might be an embed)
- if (plyr.type === 'video') {
+ if (player.type === 'video') {
var firstSource = source.sources[0];
- if ('type' in firstSource && _inArray(config.types.embed, firstSource.type)) {
- plyr.type = firstSource.type;
+ if ('type' in firstSource && inArray(config.types.embed, firstSource.type)) {
+ player.type = firstSource.type;
}
}
}
// Check for support
- plyr.supported = supported(plyr.type);
+ player.supported = checkSupport(player.type);
// Create new markup
- switch(plyr.type) {
+ switch (player.type) {
case 'video':
- plyr.media = document.createElement('video');
+ player.elements.media = createElement('video');
break;
case 'audio':
- plyr.media = document.createElement('audio');
+ player.elements.media = createElement('audio');
break;
case 'youtube':
case 'vimeo':
case 'soundcloud':
- plyr.media = document.createElement('div');
- plyr.embedId = source.sources[0].src;
+ player.elements.media = createElement('div');
+ player.embedId = source.sources[0].src;
break;
}
// Inject the new element
- _prependChild(plyr.container, plyr.media);
+ prependChild(player.elements.container, player.elements.media);
// Autoplay the new source?
- if (_is.boolean(source.autoplay)) {
+ if (is.boolean(source.autoplay)) {
config.autoplay = source.autoplay;
}
// Set attributes for audio and video
- if (_inArray(config.types.html5, plyr.type)) {
+ if (inArray(config.types.html5, player.type)) {
if (config.crossorigin) {
- plyr.media.setAttribute('crossorigin', '');
+ player.elements.media.setAttribute('crossorigin', '');
}
if (config.autoplay) {
- plyr.media.setAttribute('autoplay', '');
+ player.elements.media.setAttribute('autoplay', '');
}
if ('poster' in source) {
- plyr.media.setAttribute('poster', source.poster);
+ player.elements.media.setAttribute('poster', source.poster);
}
- if (config.loop) {
- plyr.media.setAttribute('loop', '');
+ if (config.loop.active) {
+ player.elements.media.setAttribute('loop', '');
}
}
// Restore class hooks
- _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
- _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
- _toggleStyleHook();
+ toggleClass(player.elements.container, config.classes.fullscreen.active, player.fullscreen.active);
+ toggleClass(player.elements.container, config.classes.captions.active, player.captions.enabled);
+ toggleStyleHook();
// Set new sources for html5
- if (_inArray(config.types.html5, plyr.type)) {
- _insertChildElements('source', source.sources);
+ if (inArray(config.types.html5, player.type)) {
+ insertElements('source', source.sources);
}
// Set up from scratch
- _setupMedia();
+ setupMedia();
// HTML5 stuff
- if (_inArray(config.types.html5, plyr.type)) {
+ if (inArray(config.types.html5, player.type)) {
// Setup captions
if ('tracks' in source) {
- _insertChildElements('track', source.tracks);
+ insertElements('track', source.tracks);
}
// Load HTML5 sources
- plyr.media.load();
+ player.elements.media.load();
}
// If HTML5 or embed but not fully supported, setupInterface and call ready now
- if (_inArray(config.types.html5, plyr.type) || (_inArray(config.types.embed, plyr.type) && !plyr.supported.full)) {
+ if (inArray(config.types.html5, player.type) || (inArray(config.types.embed, player.type) && !player.supported.full)) {
// Setup interface
- _setupInterface();
+ setupInterface();
// Call ready
- _ready();
+ ready();
}
// Set aria title and iframe title
config.title = source.title;
- _setTitle();
+ setTitle();
}
// Destroy instance adn wait for callback
// Vimeo throws a wobbly if you don't wait
- _destroy(setup, false);
+ destroy(setup, false);
}
// Update poster
- function _updatePoster(source) {
- if (plyr.type === 'video') {
- plyr.media.setAttribute('poster', source);
+ function updatePoster(source) {
+ if (player.type === 'video') {
+ player.elements.media.setAttribute('poster', source);
}
}
// Listen for control events
- function _controlListeners() {
+ function controlListeners() {
// IE doesn't support input event, so we fallback to change
- var inputEvent = (plyr.browser.isIE ? 'change' : 'input');
+ var inputEvent = (player.browser.isIE ? 'change' : 'input');
// Click play/pause helper
- function togglePlay() {
- var play = _togglePlay();
+ function _togglePlay() {
+ var play = togglePlay();
// Determine which buttons
- var trigger = plyr.buttons[play ? 'play' : 'pause'],
- target = plyr.buttons[play ? 'pause' : 'play'];
-
- // Get the last play button to account for the large play button
- if (target && target.length > 1) {
- target = target[target.length - 1];
- } else {
- target = target[0];
- }
+ var trigger = player.elements.buttons[play ? 'play' : 'pause'];
+ var target = player.elements.buttons[play ? 'pause' : 'play'];
// Setup focus and tab focus
if (target) {
- var hadTabFocus = _hasClass(trigger, config.classes.tabFocus);
+ var hadTabFocus = hasClass(trigger, config.classes.tabFocus);
setTimeout(function() {
target.focus();
if (hadTabFocus) {
- _toggleClass(trigger, config.classes.tabFocus, false);
- _toggleClass(target, config.classes.tabFocus, true);
+ toggleClass(trigger, config.classes.tabFocus, false);
+ toggleClass(target, config.classes.tabFocus, true);
}
}, 100);
}
}
- // Get the focused element
- function getFocusElement() {
- var focused = document.activeElement;
-
- if (!focused || focused === document.body) {
- focused = null;
- } else {
- focused = document.querySelector(':focus');
- }
-
- return focused;
- }
-
// Get the key code for an event
function getKeyCode(event) {
return event.keyCode ? event.keyCode : event.which;
@@ -2881,84 +4020,92 @@
// Detect tab focus
function checkTabFocus(focused) {
- for (var button in plyr.buttons) {
- var element = plyr.buttons[button];
+ for (var button in player.elements.buttons) {
+ var element = player.elements.buttons[button];
- if (_is.nodeList(element)) {
+ if (is.nodeList(element)) {
for (var i = 0; i < element.length; i++) {
- _toggleClass(element[i], config.classes.tabFocus, (element[i] === focused));
+ toggleClass(element[i], config.classes.tabFocus, (element[i] === focused));
}
} else {
- _toggleClass(element, config.classes.tabFocus, (element === focused));
+ toggleClass(element, config.classes.tabFocus, (element === focused));
}
}
}
// Keyboard shortcuts
- if (config.keyboardShorcuts.focused) {
+ if (config.keyboardShortcuts.focused) {
var last = null;
// Handle global presses
- if (config.keyboardShorcuts.global) {
- _on(window, 'keydown keyup', function(event) {
- var code = getKeyCode(event),
- focused = getFocusElement(),
- allowed = [48,49,50,51,52,53,54,56,57,75,77,70,67],
- count = get().length;
+ if (config.keyboardShortcuts.global) {
+ on(window, 'keydown keyup', function(event) {
+ var code = getKeyCode(event);
+ var focused = getFocusElement();
+ var allowed = [48, 49, 50, 51, 52, 53, 54, 56, 57, 75, 77, 70, 67, 73, 76, 79];
+ var count = get().length;
// Only handle global key press if there's only one player
// and the key is in the allowed keys
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
- if (count === 1 && _inArray(allowed, code) && (!_is.htmlElement(focused) || !_matches(focused, config.selectors.editable))) {
+ if (count === 1 && inArray(allowed, code) && (!is.htmlElement(focused) || !matches(focused, config.selectors.editable))) {
handleKey(event);
}
});
}
// Handle presses on focused
- _on(plyr.container, 'keydown keyup', handleKey);
+ on(player.elements.container, 'keydown keyup', handleKey);
}
function handleKey(event) {
- var code = getKeyCode(event),
- pressed = event.type === 'keydown',
- held = pressed && code === last;
+ var code = getKeyCode(event);
+ var pressed = event.type === 'keydown';
+ var held = pressed && code === last;
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
- if (!_is.number(code)) {
+ if (!is.number(code)) {
return;
}
// Seek by the number keys
function seekByKey() {
// Get current duration
- var duration = plyr.media.duration;
+ var duration = player.elements.media.duration;
// Bail if we have no duration set
- if (!_is.number(duration)) {
+ if (!is.number(duration)) {
return;
}
// Divide the max duration into 10th's and times by the number value
- _seek((duration / 10) * (code - 48));
+ seek((duration / 10) * (code - 48));
}
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
- var preventDefault = [48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67];
+ var preventDefault = [48, 49, 50, 51, 52, 53, 54, 56, 57, 32, 75, 38, 40, 77, 39, 37, 70, 67, 73, 76, 79];
+ var checkFocus = [38, 40];
+
+ if (inArray(checkFocus, code)) {
+ var focused = getFocusElement();
+
+ if (is.htmlElement(focused) && getFocusElement().type === "radio") {
+ return;
+ }
+ }
// If the code is found prevent default (e.g. prevent scrolling for arrows)
- if (_inArray(preventDefault, code)) {
+ if (inArray(preventDefault, code)) {
event.preventDefault();
event.stopPropagation();
}
- switch(code) {
- // 0-9
+ switch (code) {
case 48:
case 49:
case 50:
@@ -2968,30 +4115,77 @@
case 54:
case 55:
case 56:
- case 57: if (!held) { seekByKey(); } break;
- // Space and K key
+ case 57:
+ // 0-9
+ if (!held) {
+ seekByKey();
+ }
+ break;
+
case 32:
- case 75: if (!held) { _togglePlay(); } break;
- // Arrow up
- case 38: _increaseVolume(); break;
- // Arrow down
- case 40: _decreaseVolume(); break;
- // M key
- case 77: if (!held) { _toggleMute() } break;
- // Arrow forward
- case 39: _forward(); break;
- // Arrow back
- case 37: _rewind(); break;
- // F key
- case 70: _toggleFullscreen(); break;
- // C key
- case 67: if (!held) { _toggleCaptions(); } break;
+ case 75:
+ // Space and K key
+ if (!held) {
+ _togglePlay();
+ }
+ break;
+
+ case 38:
+ // Arrow up
+ increaseVolume();
+ break;
+
+ case 40:
+ // Arrow down
+ decreaseVolume();
+ break;
+
+ case 77:
+ // M key
+ if (!held) {
+ toggleMute();
+ }
+ break;
+
+ case 39:
+ // Arrow forward
+ forward();
+ break;
+
+ case 37:
+ // Arrow back
+ rewind();
+ break;
+
+ case 70:
+ // F key
+ toggleFullscreen();
+ break;
+
+ case 67:
+ // C key
+ if (!held) {
+ toggleCaptions();
+ }
+ break;
+
+ case 73:
+ toggleLoop('start');
+ break;
+
+ case 76:
+ toggleLoop();
+ break;
+
+ case 79:
+ toggleLoop('end');
+ break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
- if (!fullscreen.supportsFullScreen && plyr.isFullscreen && code === 27) {
- _toggleFullscreen();
+ if (!support.fullscreen && player.fullscreen.active && code === 27) {
+ toggleFullscreen();
}
// Store last code for next cycle
@@ -3002,157 +4196,247 @@
}
// Focus/tab management
- _on(window, 'keyup', function(event) {
- var code = getKeyCode(event),
- focused = getFocusElement();
+ on(window, 'keyup', function(event) {
+ var code = getKeyCode(event);
+ var focused = getFocusElement();
if (code === 9) {
checkTabFocus(focused);
}
});
- _on(document.body, 'click', function() {
- _toggleClass(_getElement('.' + config.classes.tabFocus), config.classes.tabFocus, false);
+ on(document.body, 'click', function() {
+ toggleClass(getElement('.' + config.classes.tabFocus), config.classes.tabFocus, false);
});
- for (var button in plyr.buttons) {
- var element = plyr.buttons[button];
+ for (var button in player.elements.buttons) {
+ var element = player.elements.buttons[button];
- _on(element, 'blur', function() {
- _toggleClass(element, 'tab-focus', false);
+ on(element, 'blur', function() {
+ toggleClass(element, 'tab-focus', false);
});
}
+ // Trigger custom and default handlers
+ var handlerProxy = function(event, customHandler, defaultHandler) {
+ if (is.function(customHandler)) {
+ customHandler.call(this, event);
+ }
+ if (is.function(defaultHandler)) {
+ defaultHandler.call(this, event);
+ }
+ }
+
// Play
- _proxyListener(plyr.buttons.play, 'click', config.listeners.play, togglePlay);
+ proxy(player.elements.buttons.play, 'click', config.listeners.play, _togglePlay);
+ proxy(player.elements.buttons.playLarge, 'click', config.listeners.play, _togglePlay);
// Pause
- _proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, togglePlay);
+ proxy(player.elements.buttons.pause, 'click', config.listeners.pause, _togglePlay);
- // Restart
- _proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek);
+ // Pause
+ proxy(player.elements.buttons.restart, 'click', config.listeners.restart, seek);
// Rewind
- _proxyListener(plyr.buttons.rewind, 'click', config.listeners.rewind, _rewind);
+ proxy(player.elements.buttons.rewind, 'click', config.listeners.rewind, rewind);
- // Fast forward
- _proxyListener(plyr.buttons.forward, 'click', config.listeners.forward, _forward);
+ // Rewind
+ proxy(player.elements.buttons.forward, 'click', config.listeners.forward, forward);
- // Seek
- _proxyListener(plyr.buttons.seek, inputEvent, config.listeners.seek, _seek);
+ // Mute
+ proxy(player.elements.buttons.mute, 'click', config.listeners.mute, toggleMute);
- // Set volume
- _proxyListener(plyr.volume.input, inputEvent, config.listeners.volume, function() {
- _setVolume(plyr.volume.input.value);
+ // Captions
+ proxy(player.elements.buttons.captions, 'click', config.listeners.captions, toggleCaptions);
+
+ // Fullscreen
+ proxy(player.elements.buttons.fullscreen, 'click', config.listeners.fullscreen, toggleFullscreen);
+
+ // Picture-in-Picture
+ proxy(player.elements.buttons.pip, 'click', config.listeners.pip, function(event) {
+ if (!support.pip) {
+ return;
+ }
+ player.elements.media.webkitSetPresentationMode(player.elements.media.webkitPresentationMode === 'picture-in-picture' ? 'inline' : 'picture-in-picture');
});
- // Mute
- _proxyListener(plyr.buttons.mute, 'click', config.listeners.mute, _toggleMute);
+ // Airplay
+ proxy(player.elements.buttons.airplay, 'click', config.listeners.airplay, function(event) {
+ if (!support.airplay) {
+ return;
+ }
+ player.elements.media.webkitShowPlaybackTargetPicker();
+ });
- // Fullscreen
- _proxyListener(plyr.buttons.fullscreen, 'click', config.listeners.fullscreen, _toggleFullscreen);
+ // Settings menu
+ on(player.elements.settings.menu, 'click', toggleMenu);
- // Handle user exiting fullscreen by escaping etc
- if (fullscreen.supportsFullScreen) {
- _on(document, fullscreen.fullScreenEventName, _toggleFullscreen);
- }
+ // Click anywhere closes menu
+ on(document.body, 'click', function(event) {
+ var menu = player.elements.settings.menu;
+ var form = menu.querySelector('form');
- // Captions
- _proxyListener(plyr.buttons.captions, 'click', config.listeners.captions, _toggleCaptions);
+ if (form.getAttribute('aria-hidden') === 'true' || menu.contains(event.target)) {
+ return;
+ }
+
+ // TODO: This should call some sort of menuToggle function?
+ form.setAttribute('aria-hidden', true);
+ });
+
+ // Settings menu items - use event delegation as items are added/removed
+ on(player.elements.settings.menu, 'click', function(event) {
+ // Settings - Speed
+ if (matches(event.target, config.selectors.buttons.speed)) {
+ handlerProxy.call(this, event, config.listeners.speed, function() {
+ //var speedValue = document.querySelector('[data-plyr="speed"]:checked').value;
+ //setSpeed(Number(speedValue));
+ console.warn("Set speed");
+ });
+ }
+
+ // Settings - Quality
+ else if (matches(event.target, config.selectors.buttons.quality)) {
+ handlerProxy.call(this, event, config.listeners.quality, function() {
+ console.warn("Set quality");
+ });
+ }
+
+ // Settings - Looping
+ else if (matches(event.target, config.selectors.buttons.loop)) {
+ handlerProxy.call(this, event, config.listeners.loop, function() {
+ // TODO: This should be done in the method itself I think
+ var value = event.target.getAttribute('data-loop__value') || event.target.getAttribute('data-loop__type');
+
+ if (inArray(['start', 'end', 'all', 'none'], value)) {
+ toggleLoop(value);
+ }
+ });
+ }
+
+ // Settings - Language
+ else if (matches(event.target, config.selectors.buttons.language)) {
+ handlerProxy.call(this, event, config.listeners.language, function(event) {
+ // TODO: This should be done in the method itself I think
+ var index = event.target.attributes.getNamedItem("data-index").value;
+ setCaptionIndex(index);
+ });
+ }
+ });
+
+ // Seek
+ proxy(player.elements.inputs.seek, inputEvent, config.listeners.seek, seek);
+
+ // Seek
+ proxy(player.elements.inputs.volume, inputEvent, config.listeners.volume, setVolume);
// Seek tooltip
- _on(plyr.progress.container, 'mouseenter mouseleave mousemove', _updateSeekTooltip);
+ on(player.elements.progress, 'mouseenter mouseleave mousemove', updateSeekTooltip);
// Toggle controls visibility based on mouse movement
if (config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
- _on(plyr.container, 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', _toggleControls);
+ on(player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', toggleControls);
// Watch for cursor over controls so they don't hide when trying to interact
- _on(plyr.controls, 'mouseenter mouseleave', function(event) {
- plyr.controls.hover = event.type === 'mouseenter';
+ on(player.elements.controls, 'mouseenter mouseleave', function(event) {
+ player.elements.controls.hover = event.type === 'mouseenter';
});
- // Watch for cursor over controls so they don't hide when trying to interact
- _on(plyr.controls, 'mousedown mouseup touchstart touchend touchcancel', function(event) {
- plyr.controls.pressed = _inArray(['mousedown', 'touchstart'], event.type);
+ // Watch for cursor over controls so they don't hide when trying to interact
+ on(player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function(event) {
+ player.elements.controls.pressed = inArray(['mousedown', 'touchstart'], event.type);
});
// Focus in/out on controls
- _on(plyr.controls, 'focus blur', _toggleControls, true);
+ on(player.elements.controls, 'focus blur', toggleControls, true);
}
- // Adjust volume on scroll
- _on(plyr.volume.input, 'wheel', function(event) {
- event.preventDefault();
-
+ // Mouse wheel for volume
+ proxy(player.elements.inputs.volume, 'wheel', config.listeners.volume, function(event) {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
- var inverted = event.webkitDirectionInvertedFromDevice,
- step = (config.volumeStep / 5);
+ var inverted = event.webkitDirectionInvertedFromDevice;
+ var step = (1 / 5);
+ var direction = 0;
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
- _decreaseVolume(step);
+ decreaseVolume(step);
+ direction = -1;
} else {
- _increaseVolume(step);
+ increaseVolume(step);
+ direction = 1;
}
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
- _increaseVolume(step);
+ increaseVolume(step);
+ direction = 1;
} else {
- _decreaseVolume(step);
+ decreaseVolume(step);
+ direction = -1;
}
}
+
+ // Don't break page scrolling at max and min
+ if ((direction === 1 && player.elements.media.volume < 1) ||
+ (direction === -1 && player.elements.media.volume > 0)) {
+ event.preventDefault();
+ }
});
+
+ // Handle user exiting fullscreen by escaping etc
+ if (support.fullscreen) {
+ on(document, fullscreen.eventType, toggleFullscreen);
+ }
}
// Listen for media events
- function _mediaListeners() {
+ function mediaListeners() {
// Time change on media
- _on(plyr.media, 'timeupdate seeking', _timeUpdate);
+ on(player.elements.media, 'timeupdate seeking', timeUpdate);
// Update manual captions
- _on(plyr.media, 'timeupdate', _seekManualCaptions);
+ on(player.elements.media, 'timeupdate', seekManualCaptions);
// Display duration
- _on(plyr.media, 'durationchange loadedmetadata', _displayDuration);
+ on(player.elements.media, 'durationchange loadedmetadata', displayDuration);
// Handle the media finishing
- _on(plyr.media, 'ended', function() {
+ on(player.elements.media, 'ended', function() {
// Show poster on end
- if (plyr.type === 'video' && config.showPosterOnEnd) {
+ if (player.type === 'video' && config.showPosterOnEnd) {
// Clear
- if (plyr.type === 'video') {
- _setCaption();
+ if (player.type === 'video') {
+ setCaption();
}
// Restart
- _seek();
+ seek();
// Re-load media
- plyr.media.load();
+ player.elements.media.load();
}
});
// Check for buffer progress
- _on(plyr.media, 'progress playing', _updateProgress);
+ on(player.elements.media, 'progress playing', updateProgress);
// Handle native mute
- _on(plyr.media, 'volumechange', _updateVolume);
+ on(player.elements.media, 'volumechange', updateVolume);
// Handle native play/pause
- _on(plyr.media, 'play pause ended', _checkPlaying);
+ on(player.elements.media, 'play pause ended', checkPlaying);
// Loading
- _on(plyr.media, 'waiting canplay seeked', _checkLoading);
+ on(player.elements.media, 'waiting canplay seeked', checkLoading);
// Click video
- if (config.clickToPlay && plyr.type !== 'audio') {
+ if (config.clickToPlay && player.type !== 'audio') {
// Re-fetch the wrapper
- var wrapper = _getElement('.' + config.classes.videoWrapper);
+ var wrapper = getElement('.' + config.classes.videoWrapper);
// Bail if there's no wrapper (this should never happen)
if (!wrapper) {
@@ -3163,80 +4447,82 @@
wrapper.style.cursor = "pointer";
// On click play, pause ore restart
- _on(wrapper, 'click', function() {
+ on(wrapper, 'click', function() {
// Touch devices will just show controls (if we're hiding controls)
- if (config.hideControls && plyr.browser.isTouch && !plyr.media.paused) {
+ if (config.hideControls && player.browser.isTouch && !player.elements.media.paused) {
return;
}
- if (plyr.media.paused) {
- _play();
- } else if (plyr.media.ended) {
- _seek();
- _play();
+ if (player.elements.media.paused) {
+ play();
+ } else if (player.elements.media.ended) {
+ seek();
+ play();
} else {
- _pause();
+ pause();
}
});
}
// Disable right click
if (config.disableContextMenu) {
- _on(plyr.media, 'contextmenu', function(event) { event.preventDefault(); });
+ on(player.elements.media, 'contextmenu', function(event) {
+ event.preventDefault();
+ });
}
// Proxy events to container
// Bubble up key events for Edge
- _on(plyr.media, config.events.concat(['keyup', 'keydown']).join(' '), function(event) {
- _triggerEvent(plyr.container, event.type, true);
+ on(player.elements.media, config.events.concat(['keyup', 'keydown']).join(' '), function(event) {
+ trigger(player.elements.container, event.type, true);
});
}
// Cancel current network requests
// See https://github.com/Selz/plyr/issues/174
- function _cancelRequests() {
- if (!_inArray(config.types.html5, plyr.type)) {
+ function cancelRequests() {
+ if (!inArray(config.types.html5, player.type)) {
return;
}
// Remove child sources
- var sources = plyr.media.querySelectorAll('source');
+ var sources = player.elements.media.querySelectorAll('source');
for (var i = 0; i < sources.length; i++) {
- _remove(sources[i]);
+ remove(sources[i]);
}
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
- plyr.media.setAttribute('src', config.blankUrl);
+ player.elements.media.setAttribute('src', 'https://cdn.selz.com/plyr/blank.mp4');
// Load the new empty source
// This will cancel existing requests
// See https://github.com/Selz/plyr/issues/174
- plyr.media.load();
+ player.elements.media.load();
// Debugging
- _log('Cancelled network requests');
+ log('Cancelled network requests');
}
// Destroy an instance
// Event listeners are removed when elements are removed
// http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
- function _destroy(callback, restore) {
+ function destroy(callback, restore) {
// Bail if the element is not initialized
- if (!plyr.init) {
+ if (!player.init) {
return null;
}
// Type specific stuff
- switch (plyr.type) {
+ switch (player.type) {
case 'youtube':
// Clear timers
window.clearInterval(timers.buffering);
window.clearInterval(timers.playing);
// Destroy YouTube API
- plyr.embed.destroy();
+ player.embed.destroy();
// Clean up
cleanUp();
@@ -3246,17 +4532,17 @@
case 'vimeo':
// Destroy Vimeo API
// then clean up (wait, to prevent postmessage errors)
- plyr.embed.unload().then(cleanUp);
+ player.embed.unload().then(cleanUp);
// Vimeo does not always return
- timers.cleanUp = window.setTimeout(cleanUp, 200);
+ window.setTimeout(cleanUp, 200);
break;
case 'video':
case 'audio':
// Restore native video controls
- _toggleNativeControls(true);
+ toggleNativeControls(true);
// Clean up
cleanUp();
@@ -3265,15 +4551,13 @@
}
function cleanUp() {
- clearTimeout(timers.cleanUp);
-
// Default to restore original element
- if (!_is.boolean(restore)) {
+ if (!is.boolean(restore)) {
restore = true;
}
// Callback
- if (_is.function(callback)) {
+ if (is.function(callback)) {
callback.call(original);
}
@@ -3283,217 +4567,255 @@
}
// Remove init flag
- plyr.init = false;
+ player.init = false;
// Replace the container with the original element provided
- plyr.container.parentNode.replaceChild(original, plyr.container);
+ player.elements.container.parentNode.replaceChild(original, player.elements.container);
- // Allow overflow (set on fullscreen)
+ // unbind escape key
document.body.style.overflow = '';
// Event
- _triggerEvent(original, 'destroyed', true);
+ trigger(original, 'destroyed', true);
}
}
// Setup a player
- function _init() {
+ function init() {
// Bail if the element is initialized
- if (plyr.init) {
+ if (player.init) {
return null;
}
- // Setup the fullscreen api
- fullscreen = _fullscreen();
-
// Sniff out the browser
- plyr.browser = _browserSniff();
+ player.browser = getBrowser();
// Bail if nothing to setup
- if (!_is.htmlElement(plyr.media)) {
+ if (!is.htmlElement(player.elements.media)) {
return;
}
// Load saved settings from localStorage
- _setupStorage();
+ setupStorage();
// Set media type based on tag or data attribute
// Supported: video, audio, vimeo, youtube
var tagName = media.tagName.toLowerCase();
if (tagName === 'div') {
- plyr.type = media.getAttribute('data-type');
- plyr.embedId = media.getAttribute('data-video-id');
+ player.type = media.getAttribute('data-type');
+ player.embedId = media.getAttribute('data-video-id');
// Clean up
media.removeAttribute('data-type');
media.removeAttribute('data-video-id');
} else {
- plyr.type = tagName;
- config.crossorigin = (media.getAttribute('crossorigin') !== null);
- config.autoplay = (config.autoplay || (media.getAttribute('autoplay') !== null));
- config.loop = (config.loop || (media.getAttribute('loop') !== null));
+ player.type = tagName;
+ config.crossorigin = (media.getAttribute('crossorigin') !== null);
+ config.autoplay = (config.autoplay || (media.getAttribute('autoplay') !== null));
+ config.loop = (config.loop || (media.getAttribute('loop') !== null));
}
// Check for support
- plyr.supported = supported(plyr.type);
+ player.supported = checkSupport(player.type);
// If no native support, bail
- if (!plyr.supported.basic) {
+ if (!player.supported.basic) {
return;
}
// Wrap media
- plyr.container = _wrap(media, document.createElement('div'));
+ player.elements.container = wrap(media, createElement('div'));
// Allow focus to be captured
- plyr.container.setAttribute('tabindex', 0);
+ player.elements.container.setAttribute('tabindex', 0);
// Add style hook
- _toggleStyleHook();
+ toggleStyleHook();
// Debug info
- _log('' + plyr.browser.name + ' ' + plyr.browser.version);
+ log('' + player.browser.name + ' ' + player.browser.version);
// Setup media
- _setupMedia();
+ setupMedia();
// Setup interface
// If embed but not fully supported, setupInterface (to avoid flash of controls) and call ready now
- if (_inArray(config.types.html5, plyr.type) || (_inArray(config.types.embed, plyr.type) && !plyr.supported.full)) {
+ if (inArray(config.types.html5, player.type) || (inArray(config.types.embed, player.type) && !player.supported.full)) {
// Setup UI
- _setupInterface();
+ setupInterface();
// Call ready
- _ready();
+ ready();
// Set title on button and frame
- _setTitle();
+ setTitle();
}
// Successful setup
- plyr.init = true;
+ player.init = true;
}
// Setup the UI
- function _setupInterface() {
+ function setupInterface() {
// Don't setup interface if no support
- if (!plyr.supported.full) {
- _warn('Basic support only', plyr.type);
+ if (!player.supported.full) {
+ warn('Basic support only', player.type);
// Remove controls
- _remove(_getElement(config.selectors.controls.wrapper));
+ remove(getElement(config.selectors.controls.wrapper));
// Remove large play
- _remove(_getElement(config.selectors.buttons.play));
+ remove(getElement(config.selectors.buttons.play));
// Restore native controls
- _toggleNativeControls(true);
+ toggleNativeControls(true);
// Bail
return;
}
// Inject custom controls if not present
- var controlsMissing = !_getElements(config.selectors.controls.wrapper).length;
- if (controlsMissing) {
+ if (!is.htmlElement(getElement(config.selectors.controls.wrapper))) {
// Inject custom controls
- _injectControls();
+ injectControls();
+ controlListeners();
}
// Find the elements
- if (!_findElements()) {
+ // TODO: re-enable when custom HTML is restored
+ /*if (!findElements()) {
return;
- }
-
- // If the controls are injected, re-bind listeners for controls
- if (controlsMissing) {
- _controlListeners();
- }
+ }*/
// Media element listeners
- _mediaListeners();
+ mediaListeners();
// Remove native controls
- _toggleNativeControls();
+ toggleNativeControls();
// Setup fullscreen
- _setupFullscreen();
+ setupFullscreen();
// Captions
- _setupCaptions();
+ setupCaptions();
+ setCaptionIndex();
// Set volume
- _setVolume();
- _updateVolume();
+ setVolume();
+ updateVolume();
+
+ // Set playback speed
+ setSpeed();
+
+ // Set loop
+ toggleLoop();
// Reset time display
- _timeUpdate();
+ timeUpdate();
// Update the UI
- _checkPlaying();
+ checkPlaying();
}
api = {
- getOriginal: function() { return original; },
- getContainer: function() { return plyr.container },
- getEmbed: function() { return plyr.embed; },
- getMedia: function() { return plyr.media; },
- getType: function() { return plyr.type; },
- getDuration: _getDuration,
- getCurrentTime: function() { return plyr.media.currentTime; },
- getVolume: function() { return plyr.media.volume; },
- isMuted: function() { return plyr.media.muted; },
- isReady: function() { return _hasClass(plyr.container, config.classes.ready); },
- isLoading: function() { return _hasClass(plyr.container, config.classes.loading); },
- isPaused: function() { return plyr.media.paused; },
- on: function(event, callback) { _on(plyr.container, event, callback); return this; },
- play: _play,
- pause: _pause,
- stop: function() { _pause(); _seek(); },
- restart: _seek,
- rewind: _rewind,
- forward: _forward,
- seek: _seek,
- source: _source,
- poster: _updatePoster,
- setVolume: _setVolume,
- togglePlay: _togglePlay,
- toggleMute: _toggleMute,
- toggleCaptions: _toggleCaptions,
- toggleFullscreen: _toggleFullscreen,
- toggleControls: _toggleControls,
- isFullscreen: function() { return plyr.isFullscreen || false; },
- support: function(mimeType) { return _supportMime(plyr, mimeType); },
- destroy: _destroy
+ getOriginal: function() {
+ return original;
+ },
+ getContainer: function() {
+ return player.elements.container
+ },
+ getEmbed: function() {
+ return player.embed;
+ },
+ getMedia: function() {
+ return player.elements.media;
+ },
+ getType: function() {
+ return player.type;
+ },
+ getDuration: getDuration,
+ getCurrentTime: function() {
+ return player.elements.media.currentTime;
+ },
+ getVolume: function() {
+ return player.elements.media.volume;
+ },
+ isMuted: function() {
+ return player.elements.media.muted;
+ },
+ isReady: function() {
+ return hasClass(player.elements.container, config.classes.ready);
+ },
+ isLoading: function() {
+ return hasClass(player.elements.container, config.classes.loading);
+ },
+ isPaused: function() {
+ return player.elements.media.paused;
+ },
+ isLooping: function() {
+ return config.loop.active;
+ },
+ on: function(event, callback) {
+ on(player.elements.container, event, callback);
+ return this;
+ },
+ play: play,
+ pause: pause,
+ loop: toggleLoop,
+ stop: function() {
+ pause();
+ seek();
+ },
+ restart: seek,
+ rewind: rewind,
+ forward: forward,
+ seek: seek,
+ source: source,
+ poster: updatePoster,
+ setVolume: setVolume,
+ setSpeed: setSpeed,
+ togglePlay: togglePlay,
+ toggleMute: toggleMute,
+ toggleCaptions: toggleCaptions,
+ toggleFullscreen: toggleFullscreen,
+ toggleControls: toggleControls,
+ setCaptionIndex: setCaptionIndex,
+ isFullscreen: function() {
+ return player.fullscreen.active || false;
+ },
+ support: function(mimeType) {
+ return support.mime(player, mimeType);
+ },
+ destroy: destroy
};
// Everything done
- function _ready() {
+ function ready() {
// Ready event at end of execution stack
window.setTimeout(function() {
- _triggerEvent(plyr.media, 'ready');
+ trigger(player.elements.media, 'ready');
}, 0);
// Set class hook on media element
- _toggleClass(plyr.media, defaults.classes.setup, true);
+ toggleClass(player.elements.media, defaults.classes.setup, true);
// Set container class for ready
- _toggleClass(plyr.container, config.classes.ready, true);
+ toggleClass(player.elements.container, config.classes.ready, true);
// Store a refernce to instance
- plyr.media.plyr = api;
+ player.elements.media.plyr = api;
// Autoplay
if (config.autoplay) {
- _play();
+ play();
}
}
// Initialize instance
- _init();
+ init();
// If init failed, return null
- if (!plyr.init) {
+ if (!player.init) {
return null;
}
@@ -3505,14 +4827,14 @@
var x = new XMLHttpRequest();
// If the id is set and sprite exists, bail
- if (_is.string(id) && _is.htmlElement(document.querySelector('#' + id))) {
+ if (is.string(id) && is.htmlElement(document.querySelector('#' + id))) {
return;
}
// Create placeholder (to prevent loading twice)
- var container = document.createElement('div');
+ var container = createElement('div');
container.setAttribute('hidden', '');
- if (_is.string(id)) {
+ if (is.string(id)) {
container.setAttribute('id', id);
}
document.body.insertBefore(container, document.body.childNodes[0]);
@@ -3533,79 +4855,63 @@
}
// Check for support
- function supported(type) {
- var browser = _browserSniff(),
- isOldIE = (browser.isIE && browser.version <= 9),
- isIos = browser.isIos,
- isIphone = browser.isIphone,
- audioSupport = !!document.createElement('audio').canPlayType,
- videoSupport = !!document.createElement('video').canPlayType,
- basic = false,
- full = false;
+ function checkSupport(type) {
+ var browser = getBrowser();
+ var isOldIE = (browser.isIE && browser.version <= 9);
+ var isIos = browser.isIos;
+ var isIphone = /iPhone|iPod/i.test(navigator.userAgent);
+ var audio = !!createElement('audio').canPlayType;
+ var video = !!createElement('video').canPlayType;
+ var basic;
+ var full;
switch (type) {
case 'video':
- basic = videoSupport;
- full = (basic && (!isOldIE && !isIphone));
+ basic = video;
+ full = (basic && (!isOldIE && !isIphone));
break;
case 'audio':
- basic = audioSupport;
- full = (basic && !isOldIE);
+ basic = audio;
+ full = (basic && !isOldIE);
break;
- // Vimeo does not seem to be supported on iOS via API
- // Issue raised https://github.com/vimeo/player.js/issues/87
case 'vimeo':
- basic = true;
- full = (!isOldIE && !isIos);
- break;
-
case 'youtube':
- basic = true;
- full = (!isOldIE && !isIos);
-
- // YouTube seems to work on iOS 10+ on iPad
- if (isIos && !isIphone && browser.version >= 10) {
- full = true;
- }
-
- break;
-
case 'soundcloud':
basic = true;
- full = (!isOldIE && !isIphone);
+ full = (!isOldIE && !isIos);
break;
default:
- basic = (audioSupport && videoSupport);
- full = (basic && !isOldIE);
+ basic = (audio && video);
+ full = (basic && !isOldIE);
}
return {
- basic: basic,
- full: full
+ basic: basic,
+ full: full
};
}
// Setup function
function setup(targets, options) {
// Get the players
- var players = [],
- instances = [],
- selector = [defaults.selectors.html5, defaults.selectors.embed].join(',');
+ var players = [];
+ var instances = [];
+ var selector = [defaults.selectors.html5, defaults.selectors.embed].join(',');
// Select the elements
- if (_is.string(targets)) {
+ if (is.string(targets)) {
// String selector passed
targets = document.querySelectorAll(targets);
- } else if (_is.htmlElement(targets)) {
+ } else if (is.htmlElement(targets)) {
// Single HTMLElement passed
targets = [targets];
- } else if (!_is.nodeList(targets) && !_is.array(targets) && !_is.string(targets)) {
+ } else if (!is.nodeList(targets) && !is.array(targets) && !is.string(targets)) {
// No selector passed, possibly options as first argument
// If options are the first argument
- if (_is.undefined(options) && _is.object(targets)) {
+ if (is.undefined(options) && is.object(targets)) {
options = targets;
}
@@ -3614,26 +4920,26 @@
}
// Convert NodeList to array
- if (_is.nodeList(targets)) {
+ if (is.nodeList(targets)) {
targets = Array.prototype.slice.call(targets);
}
// Bail if disabled or no basic support
// You may want to disable certain UAs etc
- if (!supported().basic || !targets.length) {
+ if (!checkSupport().basic || !targets.length) {
return false;
}
// Add to container list
function add(target, media) {
- if (!_hasClass(media, defaults.classes.hook)) {
+ if (!hasClass(media, defaults.classes.hook)) {
players.push({
// Always wrap in a <div> for styling
- //container: _wrap(media, document.createElement('div')),
+ // container: wrap(media, document.createElement('div')),
// Could be a container or the media itself
- target: target,
+ target: target,
// This should be the <video>, <audio> or <div> (YouTube/Vimeo)
- media: media
+ media: media
});
}
}
@@ -3650,7 +4956,7 @@
for (var x = 0; x < children.length; x++) {
add(target, children[x]);
}
- } else if (_matches(target, selector)) {
+ } else if (matches(target, selector)) {
// Target is media element
add(target, target);
}
@@ -3658,9 +4964,9 @@
// Create a player instance for each element
players.forEach(function(player) {
- var element = player.target,
- media = player.media,
- match = false;
+ var element = player.target;
+ var media = player.media;
+ var match = false;
// The target element can also be the media element
if (media === element) {
@@ -3672,10 +4978,11 @@
var data = {};
// Try parsing data attribute config
- try { data = JSON.parse(element.getAttribute('data-plyr')); }
- catch(e) { }
+ try {
+ data = JSON.parse(element.getAttribute('data-plyr'));
+ } catch (e) {}
- var config = _extend({}, defaults, options, data);
+ var config = extend({}, defaults, options, data);
// Bail if not enabled
if (!config.enabled) {
@@ -3686,7 +4993,7 @@
var instance = new Plyr(media, config);
// Go to next if setup failed
- if (!_is.object(instance)) {
+ if (!is.object(instance)) {
return;
}
@@ -3694,13 +5001,13 @@
if (config.debug) {
var events = config.events.concat(['setup', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']);
- _on(instance.getContainer(), events.join(' '), function(event) {
+ on(instance.getContainer(), events.join(' '), function(event) {
console.log([config.logPrefix, 'event:', event.type].join(' '), event.detail.plyr);
});
}
// Callback
- _event(instance.getContainer(), 'setup', true, {
+ event(instance.getContainer(), 'setup', true, {
plyr: instance
});
@@ -3713,21 +5020,21 @@
// Get all instances within a provided container
function get(container) {
- if (_is.string(container)) {
+ if (is.string(container)) {
// Get selector if string passed
container = document.querySelector(container);
- } else if (_is.undefined(container)) {
+ } else if (is.undefined(container)) {
// Use body by default to get all on page
container = document.body;
}
// If we have a HTML element
- if (_is.htmlElement(container)) {
+ if (is.htmlElement(container)) {
var elements = container.querySelectorAll('.' + defaults.classes.setup),
instances = [];
Array.prototype.slice.call(elements).forEach(function(element) {
- if (_is.object(element.plyr)) {
+ if (is.object(element.plyr)) {
instances.push(element.plyr);
}
});
@@ -3739,22 +5046,26 @@
}
return {
- setup: setup,
- supported: supported,
+ setup: setup,
+ supported: checkSupport,
loadSprite: loadSprite,
- get: get
+ get: get
};
}));
// Custom event polyfill
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
-(function () {
+(function() {
if (typeof window.CustomEvent === 'function') {
return;
}
function CustomEvent(event, params) {
- params = params || { bubbles: false, cancelable: false, detail: undefined };
+ params = params || {
+ bubbles: false,
+ cancelable: false,
+ detail: undefined
+ };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
diff --git a/src/less/plyr.less b/src/less/plyr.less
index c9e3d7c3..8e08cf40 100644
--- a/src/less/plyr.less
+++ b/src/less/plyr.less
@@ -11,6 +11,16 @@
@keyframes plyr-progress {
to { background-position: @plyr-progress-loading-size 0; }
}
+@keyframes plyr-popup {
+ from {
+ transform: translateY(10px);
+ opacity: .5;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
// Styles
// -------------------------------
@@ -20,6 +30,7 @@
max-width: 100%;
min-width: 200px;
font-family: @plyr-font-family;
+ font-weight: 500;
direction: ltr;
& when (@plyr-border-box = true) {
@@ -40,6 +51,11 @@
}
}
+ // ARIA
+ [aria-hidden='true'] {
+ display: none;
+ }
+
// Focus
&:focus {
outline: 0;
@@ -173,6 +189,9 @@
position: relative;
background: #000;
border-radius: inherit;
+ // Require z-index to force border-radius
+ z-index: 0;
+ overflow: hidden;
}
// Container for embeds
@@ -180,11 +199,11 @@
padding-bottom: 56.25%; /* 16:9 */
height: 0;
border-radius: inherit;
-
// Require overflow and z-index to force border-radius
overflow: hidden;
z-index: 0;
+
iframe {
position: absolute;
top: 0;
@@ -225,7 +244,6 @@
color: @plyr-captions-color;
font-size: @plyr-font-size-captions-base;
text-align: center;
- font-weight: 400;
span {
border-radius: 2px;
@@ -270,50 +288,64 @@
text-align: center;
// Spacing
- > button,
+ > .plyr__control,
.plyr__progress,
- .plyr__time {
+ .plyr__time,
+ .plyr__menu {
margin-left: (@plyr-control-spacing / 2);
- &:first-child {
+ &:first-child,
+ &:first-child + [data-plyr="pause"] {
margin-left: 0;
}
}
.plyr__volume {
margin-left: (@plyr-control-spacing / 2);
}
- [data-plyr="pause"] {
- margin-left: 0;
- }
- // Buttons
- button {
- position: relative;
- display: inline-block;
- flex-shrink: 0;
- overflow: visible; // IE11
- vertical-align: middle;
- padding: @plyr-control-padding;
- border: 0;
- background: transparent;
- border-radius: 3px;
- cursor: pointer;
- transition: background .3s ease, color .3s ease, opacity .3s ease;
- color: inherit;
-
- svg {
- width: @plyr-control-icon-size;
- height: @plyr-control-icon-size;
- display: block;
- fill: currentColor;
+ @media (min-width: @plyr-bp-screen-sm) {
+ > .plyr__control,
+ .plyr__progress,
+ .plyr__time,
+ .plyr__menu {
+ margin-left: @plyr-control-spacing;
}
- // Default focus
- &:focus {
- outline: 0;
+ > .plyr__control + .plyr__control,
+ .plyr__menu + .plyr__control,
+ > .plyr__control + .plyr__menu {
+ margin-left: (@plyr-control-spacing / 2);
}
}
+}
+// Hide controls
+.plyr--hide-controls .plyr__controls {
+ opacity: 0;
+ pointer-events: none;
+}
+
+// Buttons
+.plyr__control {
+ position: relative;
+ display: inline-block;
+ flex-shrink: 0;
+ overflow: visible; // IE11
+ vertical-align: middle;
+ padding: @plyr-control-padding;
+ border: 0;
+ background: transparent;
+ border-radius: 3px;
+ cursor: pointer;
+ transition: background .3s ease, color .3s ease, opacity .3s ease;
+ color: inherit;
+ svg {
+ width: @plyr-control-icon-size;
+ height: @plyr-control-icon-size;
+ display: block;
+ fill: currentColor;
+ pointer-events: none;
+ }
// Hide toggle icons by default
.icon--exit-fullscreen,
.icon--muted,
@@ -321,19 +353,11 @@
display: none;
}
- @media (min-width: @plyr-bp-screen-sm) {
- > button,
- .plyr__progress,
- .plyr__time {
- margin-left: @plyr-control-spacing;
- }
+ // Default focus
+ &:focus {
+ outline: 0;
}
}
-// Hide controls
-.plyr--hide-controls .plyr__controls {
- opacity: 0;
- pointer-events: none;
-}
// Video controls
.plyr--video .plyr__controls {
@@ -343,16 +367,17 @@
bottom: 0;
z-index: 2;
padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing;
- background: linear-gradient(fade(@plyr-video-controls-bg, 0%), fade(@plyr-video-controls-bg, 50%));
+ background: linear-gradient(fade(@plyr-video-controls-bg, 0%), fade(@plyr-video-controls-bg, 70%));
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
color: @plyr-video-control-color;
transition: opacity .3s ease;
- button {
+ .plyr__control {
// Hover and tab focus
&.tab-focus:focus,
- &:hover {
+ &:hover,
+ &[aria-expanded='true'] {
background: @plyr-video-control-bg-hover;
color: @plyr-video-control-color-hover;
}
@@ -367,10 +392,11 @@
border: @plyr-audio-controls-border;
color: @plyr-audio-control-color;
- button {
+ .plyr__control {
// Hover and tab focus
&.tab-focus:focus,
- &:hover {
+ &:hover,
+ &[aria-expanded='true'] {
background: @plyr-audio-control-bg-hover;
color: @plyr-audio-control-color-hover;
}
@@ -400,6 +426,7 @@
height: 20px;
display: block;
fill: currentColor;
+ pointer-events: none;
}
&:focus {
@@ -427,9 +454,9 @@
}
// Change icons on state change
-.plyr--fullscreen-active .icon--exit-fullscreen,
-.plyr--muted .plyr__controls .icon--muted,
-.plyr--captions-active .plyr__controls .icon--captions-on {
+.plyr--fullscreen-active .plyr__control .icon--exit-fullscreen,
+.plyr--muted .plyr__control .icon--muted,
+.plyr--captions-active .plyr__control .icon--captions-on {
display: block;
& + svg {
@@ -439,14 +466,173 @@
// Some options are hidden by default
.plyr [data-plyr='captions'],
-.plyr [data-plyr='fullscreen'] {
+.plyr [data-plyr='fullscreen'],
+.plyr [data-plyr='pip'],
+.plyr [data-plyr='airplay'] {
display: none;
}
.plyr--captions-enabled [data-plyr='captions'],
-.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
+.plyr--fullscreen-enabled [data-plyr='fullscreen'],
+.plyr--pip-enabled [data-plyr='pip'],
+.plyr--airplay-enabled [data-plyr='airplay'] {
display: inline-block;
}
+// Menus
+// --------------------------------------------------------------
+.plyr__menu {
+ position: relative;
+
+ // Hide tooltip
+ .plyr__control svg {
+ transition: transform .3s ease;
+ }
+ .plyr__control[aria-expanded='true'] {
+ svg {
+ transform: rotate(45deg);
+ }
+ .plyr__tooltip {
+ display: none;
+ }
+ }
+
+ // The actual menu container
+ &__container {
+ position: absolute;
+ z-index: 1;
+ bottom: 100%;
+ right: -5px;
+ margin-bottom: 10px;
+ animation: plyr-popup .2s ease;
+
+ background: @plyr-menu-bg;
+ border-radius: 4px;
+
+ white-space: nowrap;
+ text-align: left;
+ color: @plyr-menu-color;
+ font-size: @plyr-font-size-small;
+
+ > div {
+ overflow: hidden;
+ transition: height .35s cubic-bezier(.4,0,.2,1), width .35s cubic-bezier(.4,0,.2,1);
+ }
+
+ // Arrow
+ &::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ right: 15px;
+ height: 0;
+ width: 0;
+ border: 6px solid transparent;
+ border-top-color: @plyr-menu-bg;
+ }
+
+ ul {
+ margin: 0;
+ padding: @plyr-control-padding;
+ list-style: none;
+ overflow: hidden;
+ }
+
+ // Options
+ .plyr__control {
+ display: flex;
+ width: 100%;
+ padding: @plyr-control-padding (@plyr-control-padding * 2);
+ color: @plyr-menu-color;
+ font-weight: 600;
+ user-select: none;
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ border: 5px solid transparent;
+ }
+
+ &--forward {
+ padding-right: ceil(@plyr-control-padding * 4);
+
+ &::after {
+ right: 5px;
+ border-left-color: fade(@plyr-menu-color, 80%);
+ }
+ }
+
+ &--back {
+ position: relative;
+ @horizontal-padding: (@plyr-control-padding * 2);
+ width: ~"calc(100% - @{horizontal-padding})";
+ margin: @plyr-control-padding;
+ margin-bottom: floor(@plyr-control-padding / 2);
+ padding-left: ceil(@plyr-control-padding * 4);
+ font-weight: 500;
+
+ &::after {
+ left: 5px;
+ border-right-color: fade(@plyr-menu-color, 80%);
+ }
+ &::before {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: 1px;
+ overflow: hidden;
+ margin-top: ceil(@plyr-control-padding / 2);
+ background: fade(#000, 15%);
+ box-shadow: 0 1px 0 fade(#fff, 10%);
+ }
+ }
+ }
+
+ label.plyr__control {
+ padding-left: ceil(@plyr-control-padding * 2.5);
+
+ input[type="radio"] {
+ position: relative;
+ left: -@plyr-control-padding;
+ }
+ }
+
+ // Option value
+ .plyr__menu__value {
+ display: inherit;
+ margin-left: auto;
+ padding-left: ceil(@plyr-control-padding * 3.5);
+ pointer-events: none;
+ overflow: hidden;
+ font-weight: 500;
+ color: fade(@plyr-menu-color, 80%);
+
+ .plyr__badge {
+ font-weight: 600;
+ }
+ }
+
+ // When animating between menus
+ &.is-resizing {
+ overflow: hidden;
+ transition: height .35s cubic-bezier(.4,0,.2,1), width .35s cubic-bezier(.4,0,.2,1);
+ }
+ }
+}
+
+// Badge
+// --------------------------------------------------------------
+.plyr__badge {
+ padding: 2px 4px;
+ border-radius: 2px;
+ background: @plyr-menu-color;
+ color: @plyr-menu-bg;
+ font-size: 10px;
+}
+
// Tooltips
// --------------------------------------------------------------
.plyr__tooltip {
@@ -463,6 +649,7 @@
color: @plyr-tooltip-color;
font-size: @plyr-font-size-small;
+ font-weight: 500;
line-height: 1.3;
transform: translate(-50%, 10px) scale(.8);
@@ -486,18 +673,19 @@
z-index: 2;
}
}
-.plyr button:hover .plyr__tooltip,
-.plyr button.tab-focus:focus .plyr__tooltip,
+.plyr .plyr__control:hover .plyr__tooltip,
+.plyr .plyr__control.tab-focus:focus .plyr__tooltip,
.plyr__tooltip--visible {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
-.plyr button:hover .plyr__tooltip {
+.plyr .plyr__control:hover .plyr__tooltip {
z-index: 3;
}
// First tooltip
-.plyr__controls button:first-child .plyr__tooltip {
+.plyr__controls > .plyr__control:first-child .plyr__tooltip,
+.plyr__controls > .plyr__control:first-child + .plyr__control .plyr__tooltip {
left: 0;
transform: translate(0, 10px) scale(.8);
transform-origin: 0 100%;
@@ -508,7 +696,7 @@
}
// Last tooltip
-.plyr__controls button:last-child .plyr__tooltip {
+.plyr__controls > .plyr__control:last-child .plyr__tooltip {
right: 0;
transform: translate(0, 10px) scale(.8);
transform-origin: 100% 100%;
@@ -520,8 +708,9 @@
}
}
-.plyr__controls button:first-child,
-.plyr__controls button:last-child {
+.plyr__controls > .plyr__control:first-child,
+.plyr__controls > .plyr__control:first-child + .plyr__control,
+.plyr__controls > .plyr__control:last-child {
&:hover .plyr__tooltip,
&.tab-focus:focus .plyr__tooltip,
.plyr__tooltip--visible {
diff --git a/src/less/variables.less b/src/less/variables.less
index 807d0854..bcc80a02 100644
--- a/src/less/variables.less
+++ b/src/less/variables.less
@@ -17,7 +17,7 @@
@plyr-font-size-base: 16px;
// Captions
-@plyr-captions-bg: fade(#000, 70%);
+@plyr-captions-bg: fade(#000, 60%);
@plyr-captions-color: #fff;
@plyr-font-size-captions-base: @plyr-font-size-base;
@plyr-font-size-captions-medium: ceil(@plyr-font-size-base * 1.5);
@@ -38,15 +38,20 @@
@plyr-audio-control-bg-hover: @plyr-color-main;
// Tooltips
-@plyr-tooltip-bg: fade(#000, 70%);
+@plyr-tooltip-bg: fade(#343f4a, 90%);
@plyr-tooltip-color: #fff;
@plyr-tooltip-padding: (@plyr-control-spacing / 2);
@plyr-tooltip-arrow-size: 4px;
@plyr-tooltip-radius: 3px;
+// Menus
+@plyr-menu-bg: @plyr-tooltip-bg;
+@plyr-menu-color: @plyr-tooltip-color;
+@plyr-menu-arrow-size: 6px;
+
// Progress
@plyr-progress-loading-size: 25px;
-@plyr-progress-loading-bg: fade(#000, 15%);
+@plyr-progress-loading-bg: fade(#343f4a, 20%);
@plyr-video-progress-bg: fade(#fff, 25%);
@plyr-video-progress-buffered-bg: @plyr-video-progress-bg;
@plyr-audio-progress-bg: fade(#C6D6DB, 66%);
@@ -58,7 +63,7 @@
@plyr-range-thumb-width: floor(@plyr-range-track-height * 2);
@plyr-range-thumb-bg: #fff;
@plyr-range-thumb-border: 2px solid transparent;
-@plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(#000, 15%);
+@plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(#343f4a, 20%);
@plyr-range-thumb-active-border-color: #fff;
@plyr-range-thumb-active-bg: @plyr-video-control-bg-hover;
@plyr-range-thumb-active-scale: 1.25;
diff --git a/src/scss/plyr.scss b/src/scss/plyr.scss
index 91a5d1b2..e75a2eda 100644
--- a/src/scss/plyr.scss
+++ b/src/scss/plyr.scss
@@ -172,6 +172,9 @@
position: relative;
background: #000;
border-radius: inherit;
+ // Require z-index to force border-radius
+ z-index: 0;
+ overflow: hidden;
}
// Container for embeds
@@ -269,21 +272,20 @@
text-align: center;
// Spacing
- > button,
+ > .plyr__control,
.plyr__progress,
- .plyr__time {
+ .plyr__time,
+ .plyr__menu {
margin-left: ($plyr-control-spacing / 2);
- &:first-child {
+ &:first-child,
+ &:first-child + [data-plyr="pause"] {
margin-left: 0;
}
}
.plyr__volume {
margin-left: ($plyr-control-spacing / 2);
}
- [data-plyr="pause"] {
- margin-left: 0;
- }
// Buttons
button {
@@ -305,6 +307,7 @@
height: $plyr-control-icon-size;
display: block;
fill: currentColor;
+ pointer-events: none;
}
// Default focus
@@ -398,6 +401,7 @@
height: 20px;
display: block;
fill: currentColor;
+ pointer-events: none;
}
&:focus {
@@ -495,7 +499,8 @@
}
// First tooltip
-.plyr__controls button:first-child .plyr__tooltip {
+.plyr__controls > button:first-child .plyr__tooltip,
+.plyr__controls > button:first-child + button .plyr__tooltip {
left: 0;
transform: translate(0, 10px) scale(.8);
transform-origin: 0 100%;
@@ -506,7 +511,7 @@
}
// Last tooltip
-.plyr__controls button:last-child .plyr__tooltip {
+.plyr__controls > button:last-child .plyr__tooltip {
right: 0;
transform: translate(0, 10px) scale(.8);
transform-origin: 100% 100%;
@@ -518,8 +523,9 @@
}
}
-.plyr__controls button:first-child,
-.plyr__controls button:last-child {
+.plyr__controls > button:first-child,
+.plyr__controls > button:first-child + button,
+.plyr__controls > button:last-child {
&:hover .plyr__tooltip,
&.tab-focus:focus .plyr__tooltip,
.plyr__tooltip--visible {
diff --git a/src/scss/variables.scss b/src/scss/variables.scss
index da75f11a..39b147da 100644
--- a/src/scss/variables.scss
+++ b/src/scss/variables.scss
@@ -18,7 +18,7 @@ $plyr-font-size-small: 14px !default;
$plyr-font-size-base: 16px !default;
// Captions
-$plyr-captions-bg: transparentize(#000, .3) !default;
+$plyr-captions-bg: transparentize(#000, .4) !default;
$plyr-captions-color: #fff !default;
$plyr-font-size-captions-base: $plyr-font-size-base !default;
$plyr-font-size-captions-medium: ceil($plyr-font-size-base * 1.5) !default;
diff --git a/src/sprite/plyr-airplay.svg b/src/sprite/plyr-airplay.svg
new file mode 100644
index 00000000..45c55414
--- /dev/null
+++ b/src/sprite/plyr-airplay.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+ <path d="M16,1 L2,1 C1.447,1 1,1.447 1,2 L1,12 C1,12.553 1.447,13 2,13 L5,13 L5,11 L3,11 L3,3 L15,3 L15,11 L13,11 L13,13 L16,13 C16.553,13 17,12.553 17,12 L17,2 C17,1.447 16.553,1 16,1 L16,1 Z"></path>
+ <polygon points="4 17 14 17 9 11"></polygon>
+ </g>
+</svg> \ No newline at end of file
diff --git a/src/sprite/plyr-pip.svg b/src/sprite/plyr-pip.svg
new file mode 100644
index 00000000..d841fce5
--- /dev/null
+++ b/src/sprite/plyr-pip.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+ <polygon points="13.293 3.293 7.022 9.564 8.436 10.978 14.707 4.707 17 7 17 1 11 1"></polygon>
+ <path d="M13,15 L3,15 L3,5 L8,5 L8,3 L2,3 C1.448,3 1,3.448 1,4 L1,16 C1,16.552 1.448,17 2,17 L14,17 C14.552,17 15,16.552 15,16 L15,10 L13,10 L13,15 L13,15 Z"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/src/sprite/plyr-settings.svg b/src/sprite/plyr-settings.svg
new file mode 100644
index 00000000..fbf8ecd1
--- /dev/null
+++ b/src/sprite/plyr-settings.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+ <path d="M16.135,7.784 C14.832,7.458 14.214,5.966 14.905,4.815 C15.227,4.279 15.13,3.817 14.811,3.499 L14.501,3.189 C14.183,2.871 13.721,2.774 13.185,3.095 C12.033,3.786 10.541,3.168 10.216,1.865 C10.065,1.258 9.669,1 9.219,1 L8.781,1 C8.331,1 7.936,1.258 7.784,1.865 C7.458,3.168 5.966,3.786 4.815,3.095 C4.279,2.773 3.816,2.87 3.498,3.188 L3.188,3.498 C2.87,3.816 2.773,4.279 3.095,4.815 C3.786,5.967 3.168,7.459 1.865,7.784 C1.26,7.935 1,8.33 1,8.781 L1,9.219 C1,9.669 1.258,10.064 1.865,10.216 C3.168,10.542 3.786,12.034 3.095,13.185 C2.773,13.721 2.87,14.183 3.189,14.501 L3.499,14.811 C3.818,15.13 4.281,15.226 4.815,14.905 C5.967,14.214 7.459,14.832 7.784,16.135 C7.935,16.742 8.331,17 8.781,17 L9.219,17 C9.669,17 10.064,16.742 10.216,16.135 C10.542,14.832 12.034,14.214 13.185,14.905 C13.72,15.226 14.182,15.13 14.501,14.811 L14.811,14.501 C15.129,14.183 15.226,13.72 14.905,13.185 C14.214,12.033 14.832,10.541 16.135,10.216 C16.742,10.065 17,9.669 17,9.219 L17,8.781 C17,8.33 16.74,7.935 16.135,7.784 L16.135,7.784 Z M9,12 C7.343,12 6,10.657 6,9 C6,7.343 7.343,6 9,6 C10.657,6 12,7.343 12,9 C12,10.657 10.657,12 9,12 L9,12 Z"></path>
+ </g>
+</svg> \ No newline at end of file