diff options
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | .jsbeautifyrc | 23 | ||||
-rw-r--r-- | .jshintrc | 4 | ||||
-rw-r--r-- | bundles.json | 9 | ||||
-rw-r--r-- | demo/dist/demo.js | 2 | ||||
-rw-r--r-- | demo/index.html | 8 | ||||
-rw-r--r-- | demo/src/js/lib/sprite.js | 89 | ||||
-rw-r--r-- | demo/src/js/main.js | 164 | ||||
-rw-r--r-- | demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt | 29 | ||||
-rw-r--r-- | demo/webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt | 29 | ||||
-rw-r--r-- | dist/blank.mp4 | bin | 0 -> 1777 bytes | |||
-rw-r--r-- | dist/plyr.css | 2 | ||||
-rw-r--r-- | dist/plyr.js | 4 | ||||
-rw-r--r-- | dist/plyr.svg | 2 | ||||
-rw-r--r-- | gulpfile.js | 32 | ||||
-rw-r--r-- | notes.md | 2 | ||||
-rw-r--r-- | package.json | 12 | ||||
-rw-r--r-- | readme.md | 63 | ||||
-rw-r--r-- | src/js/plyr.js | 5341 | ||||
-rw-r--r-- | src/less/plyr.less | 315 | ||||
-rw-r--r-- | src/less/variables.less | 13 | ||||
-rw-r--r-- | src/scss/plyr.scss | 26 | ||||
-rw-r--r-- | src/scss/variables.scss | 2 | ||||
-rw-r--r-- | src/sprite/plyr-airplay.svg | 7 | ||||
-rw-r--r-- | src/sprite/plyr-pip.svg | 7 | ||||
-rw-r--r-- | src/sprite/plyr-settings.svg | 6 |
26 files changed, 3853 insertions, 2347 deletions
@@ -3,9 +3,10 @@ node_modules *.sublime-workspace .DS_Store aws.json -docs/index.dev.html *.mp4 -index-dev.html +!dist/blank.mp4 +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 } } @@ -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/bundles.json b/bundles.json index d6fdf733..245c1d43 100644 --- a/bundles.json +++ b/bundles.json @@ -1,22 +1,23 @@ { "plyr": { "less": { - "plyr.css": ["src/less/plyr.less"] + "plyr.css": ["src/less/plyr.less"] }, "scss": { - "plyr.css": ["src/scss/plyr.scss"] + "plyr.css": ["src/scss/plyr.scss"] }, "js": { - "plyr.js": ["src/js/plyr.js"] + "plyr.js": ["src/js/plyr.js"] } }, "demo": { "less": { - "demo.css": ["demo/src/less/demo.less"] + "demo.css": ["demo/src/less/demo.less"] }, "js": { "demo.js": [ "demo/src/js/lib/classlist.js", + "demo/src/js/lib/sprite.js", "demo/src/js/main.js" ] } diff --git a/demo/dist/demo.js b/demo/dist/demo.js index e21530ec..5418f747 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 – “It All Began With A Burst”",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:"147865858",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,n=arguments.length;for(i=0;i<n;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",n=e.Element[i],o=Object,r=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},s=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 s.call(e,t)},l=function(e){for(var t=r.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],n=0,o=i.length;n<o;n++)this.push(i[n]);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,n=t.length,o=!1;do e=t[i]+"",c(this,e)===-1&&(this.push(e),o=!0);while(++i<n);o&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,n=0,o=i.length,r=!1;do for(e=i[n]+"",t=c(this,e);t!==-1;)this.splice(t,1),r=!0,t=c(this,e);while(++n<o);r&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),n=i?t!==!0&&"remove":t!==!1&&"add";return n&&this[n](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(n,t,p)}catch(e){e.number===-2146823252&&(p.enumerable=!1,o.defineProperty(n,t,p))}}else o[i].__defineGetter__&&n.__defineGetter__(t,d)}}(self)),function(){window.loadSprite=function(e,t){function i(e,t){e.innerHTML=t,n.insertBefore(e,n.childNodes[0])}if("string"==typeof e){var n=document.body,o="cache-",r="string"==typeof t,s=!1,a=function(){if(!r)return!1;var e="___test";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(e){return!1}}();if(!r||0===document.querySelectorAll("#"+t).length){var c=document.createElement("div");if(c.setAttribute("hidden",""),r&&c.setAttribute("id",t),a){var l=localStorage.getItem(o+t);if(s=null!==l){var u=JSON.parse(l);i(c,u.content)}}var d=new XMLHttpRequest;if(!("withCredentials"in d))return;d.open("GET",e,!0),d.onload=function(){a&&localStorage.setItem(o+t,JSON.stringify({content:d.responseText})),i(c,d.responseText)},d.send()}}}}(),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var n=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=n+(i?" "+t:"")}}function t(t,s){if(t in o&&(s||t!==r)&&(r.length||t!==o.video)){switch(t){case o.video:i.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:i.source({type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",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:i.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://www.youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]});break;case o.vimeo:i.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://vimeo.com/76979871",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)}}document.body.addEventListener("ready",function(e){console.log(e)});var i=new Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"]});window.loadSprite("dist/demo.svg","demo-sprite");var n=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),s=window.history&&window.history.pushState;if([].forEach.call(n,function(e){e.addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),s&&history.pushState({type:e},"","#"+e)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),s){var a=!r.length;a&&(r=o.video),r in o&&history.replaceState({type:r},"",a?"":"#"+r),r!==o.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,n,o,r,s){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,r=t.createElement(i),s=t.getElementsByTagName(i)[0],r.async=1,r.src=n,s.parentNode.insertBefore(r,s)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
\ No newline at end of file diff --git a/demo/index.html b/demo/index.html index b21fa6cc..c844dcd7 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 controls crossorigin playsinline poster="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player"> <!-- 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/lib/sprite.js b/demo/src/js/lib/sprite.js new file mode 100644 index 00000000..bcd8a0b7 --- /dev/null +++ b/demo/src/js/lib/sprite.js @@ -0,0 +1,89 @@ +// ========================================================================== +// SVG sprite loading and caching +// This file should be at the top of the body to avoid a flash +// Usage: loadSprite('https://cdn.com/path/to/sprite.svg', 'sprite-id'); +// The second argument is optional but prevents loading twice +// ========================================================================== + +(function() { + window.loadSprite = function(url, id) { + if (typeof url !== "string") { + return; + } + + var body = document.body; + var prefix = "cache-"; + var hasId = typeof id === "string"; + var isCached = false; + + // Check for *actual* storage support + var cacheSupported = (function() { + if (!hasId) { + return false; + } + var test = '___test'; + try { + localStorage.setItem(test, test); + localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } + })(); + + function updateSprite(container, data) { + // Inject content + container.innerHTML = data; + + // Inject the SVG to the body + body.insertBefore(container, body.childNodes[0]); + } + + // Only load once + if (!hasId || document.querySelectorAll("#" + id).length === 0) { + // Create container + var container = document.createElement("div"); + container.setAttribute("hidden", ""); + + if (hasId) { + container.setAttribute("id", id); + } + + // Check in cache + if (cacheSupported) { + var cached = localStorage.getItem(prefix + id); + isCached = cached !== null; + + if (isCached) { + var data = JSON.parse(cached); + updateSprite(container, data.content); + } + } + + // ReSharper disable once InconsistentNaming + var xhr = new XMLHttpRequest(); + + // XHR for Chrome/Firefox/Opera/Safari + if ("withCredentials" in xhr) { + xhr.open("GET", url, true); + } + // Not supported + else { + return; + } + + // Once loaded, inject to container and body + xhr.onload = function() { + if (cacheSupported) { + localStorage.setItem(prefix + id, JSON.stringify({ + content: xhr.responseText + })); + } + + updateSprite(container, xhr.responseText); + }; + + xhr.send(); + } + } +})(); diff --git a/demo/src/js/main.js b/demo/src/js/main.js index e2926df5..b5d5ad15 100644 --- a/demo/src/js/main.js +++ b/demo/src/js/main.js @@ -4,76 +4,92 @@ // Please see readme.md in the root or github.com/selz/plyr // ========================================================================== -/*global plyr*/ +/*global Plyr*/ // General functions (function() { - //document.body.addEventListener('ready', function(event) { console.log(event); }); + 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', + var player = new Plyr('#player', { + debug: true, + title: 'View From A Blue Moon', + 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'); - - // Plyr returns an array regardless - var player = instances[0]; + window.loadSprite('dist/demo.svg', 'demo-sprite'); // Setup type toggle - var buttons = document.querySelectorAll('[data-source]'), - types = { - video: 'video', - audio: 'audio', - youtube: 'youtube', - vimeo: 'vimeo' - }, - currentType = window.location.hash.replace('#', ''), - historySupport = (window.history && window.history.pushState); + var buttons = document.querySelectorAll('[data-source]'); + var types = { + video: 'video', + audio: 'audio', + youtube: 'youtube', + vimeo: 'vimeo' + }; + var currentType = window.location.hash.replace('#', ''); + var historySupport = (window.history && window.history.pushState); // Bind to each button - for (var i = buttons.length - 1; i >= 0; i--) { - buttons[i].addEventListener('click', function() { + [].forEach.call(buttons, function(button) { + button.addEventListener('click', function() { var type = this.getAttribute('data-source'); 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 +99,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 +109,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 +135,37 @@ case types.audio: player.source({ - type: 'audio', - title: 'Kishi Bashi – “It All Began With A Burst”', + type: 'audio', + title: 'Kishi Bashi – “It All Began With A Burst”', 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: 'https://www.youtube.com/watch?v=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: '147865858', - type: 'vimeo' + src: 'https://vimeo.com/76979871', + type: 'vimeo' }] }); break; @@ -169,17 +180,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'); - ga('create', 'UA-40881672-11', 'auto'); - ga('send', 'pageview'); +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'); + window.ga('create', 'UA-40881672-11', 'auto'); + window.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/blank.mp4 b/dist/blank.mp4 Binary files differnew file mode 100644 index 00000000..d8982b63 --- /dev/null +++ b/dist/blank.mp4 diff --git a/dist/plyr.css b/dist/plyr.css index e92820ce..6e73e410 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-wrapper{position:relative;background:#000;border-radius:inherit;z-index:0;overflow:hidden}.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 div{display:inline}.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 8016e06a..569d3673 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.13/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(n,u){function c(e,t,n,s){r.event(e,t,n,r.extend({},s,{plyr:Me}))}function d(e){return Oe.elements.container.querySelectorAll(e)}function p(e){return d(e)[0]}function m(e){r.is.string(e)?(r.removeElement(Oe.elements[e]),Oe.elements[e]=null):r.removeElement(e)}function f(){function e(e){9===e.which&&Oe.fullscreen.active&&(e.target!==s||e.shiftKey?e.target===n&&e.shiftKey&&(e.preventDefault(),s.focus()):(e.preventDefault(),n.focus()))}var t=d("input:not([disabled]), button:not([disabled])"),n=t[0],s=t[t.length-1];r.on(Oe.elements.container,"keydown",e,!1)}function y(e,t){r.is.string(t)?r.insertElement(e,Oe.elements.media,{src:t}):r.is.array(t)&&t.forEach(function(t){r.insertElement(e,Oe.elements.media,t)})}function g(){return{url:Ve.iconUrl,absolute:0===Ve.iconUrl.indexOf("http")||Oe.browser.isIE}}function b(e,n){var s="http://www.w3.org/2000/svg",a=g(),l=(a.absolute?"":a.url)+"#"+Ve.iconPrefix,i=t.createElementNS(s,"svg");r.setAttributes(i,r.extend(n,{role:"presentation"}));var o=t.createElementNS(s,"use");return o.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l+"-"+e),i.appendChild(o),i}function v(e){var t=Ve.i18n[e];switch(e){case"pip":t="PIP";break;case"airplay":t="AirPlay"}return r.createElement("span",{class:Ve.classes.hidden},t)}function h(e){var t=r.createElement("span",{class:Ve.classes.menu.value});return t.appendChild(r.createElement("span",{class:Ve.classes.menu.badge},e)),t}function k(e,t){var n,s,a,l=r.createElement("button");switch(r.is.object(t)||(t={}),"class"in t?t.class.indexOf(Ve.classes.control)===-1&&(t.class+=" "+Ve.classes.control):t.class=Ve.classes.control,e){case"mute":a="toggleMute",n="volume",s="muted";break;case"captions":a="toggleCaptions",n="captions-off",s="captions-on";break;case"fullscreen":a="toggleFullscreen",n="enter-fullscreen",s="exit-fullscreen";break;case"play-large":t.class="plyr__play-large",e="play",a="play",n="play";break;default:a=e,n=e}return r.extend(t,r.getAttributesFromSelector(Ve.selectors.buttons[e],t)),r.is.string(s)&&l.appendChild(b(s,{class:"icon--"+s})),l.appendChild(b(n)),l.appendChild(v(a)),r.setAttributes(l,t),Oe.elements.buttons[e]=l,l}function C(e,t){var n=r.createElement("label",{for:t.id,class:Ve.classes.hidden},Ve.i18n[e]),s=r.createElement("input",r.extend(r.getAttributesFromSelector(Ve.selectors.inputs[e]),{type:"range",min:0,max:100,step:.1,value:0,autocomplete:"off"},t));return Oe.elements.inputs[e]=s,{label:n,input:s}}function E(e,t){var n=r.createElement("progress",r.extend(r.getAttributesFromSelector(Ve.selectors.display[e]),{min:0,max:100,value:0},t));if("volume"!==e){n.appendChild(r.createElement("span",null,"0"));var s="";switch(e){case"played":s=Ve.i18n.played;break;case"buffer":s=Ve.i18n.buffered}n.textContent="% "+s.toLowerCase()}return Oe.elements.display[e]=n,n}function A(e){var t=r.createElement("span",{class:"plyr__time"});return t.appendChild(r.createElement("span",{class:Ve.classes.hidden},Ve.i18n[e])),t.appendChild(r.createElement("span",r.getAttributesFromSelector(Ve.selectors.display[e]),"00:00")),Oe.elements.display[e]=t,t}function w(e){var t=r.createElement("div",r.getAttributesFromSelector(Ve.selectors.controls.wrapper));if(r.inArray(Ve.controls,"restart")&&t.appendChild(k("restart")),r.inArray(Ve.controls,"rewind")&&t.appendChild(k("rewind")),r.inArray(Ve.controls,"play")&&(t.appendChild(k("play")),t.appendChild(k("pause"))),r.inArray(Ve.controls,"fast-forward")&&t.appendChild(k("fast-forward")),r.inArray(Ve.controls,"progress")){var n=r.createElement("span",r.getAttributesFromSelector(Ve.selectors.progress)),s=C("seek",{id:"plyr-seek-"+e.id});if(n.appendChild(s.label),n.appendChild(s.input),n.appendChild(E("played")),n.appendChild(E("buffer")),Ve.tooltips.seek){var a=r.createElement("span",{role:"tooltip",class:Ve.classes.tooltip},"00:00");n.appendChild(a),Oe.elements.display.seekTooltip=a}Oe.elements.progress=n,t.appendChild(Oe.elements.progress)}if(r.inArray(Ve.controls,"current-time")&&t.appendChild(A("currentTime")),r.inArray(Ve.controls,"duration")&&t.appendChild(A("duration")),r.inArray(Ve.controls,"mute")&&t.appendChild(k("mute")),r.inArray(Ve.controls,"volume")){var l=r.createElement("span",{class:"plyr__volume"}),i={max:10,value:Ve.volume},u=C("volume",r.extend(i,{id:"plyr-volume-"+e.id}));l.appendChild(u.label),l.appendChild(u.input);var c=E("volume",i);l.appendChild(c),t.appendChild(l)}if(r.inArray(Ve.controls,"captions")&&t.appendChild(k("captions")),r.inArray(Ve.controls,"settings")){var d=r.createElement("span",r.extend(r.getAttributesFromSelector(Ve.selectors.buttons.settings),{class:"plyr__menu"}));d.appendChild(k("settings",{id:"plyr-settings-toggle-"+e.id,"aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id,"aria-expanded":!1}));var p=r.createElement("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}),m=r.createElement("div"),f=r.createElement("div",{id:"plyr-settings-"+e.id+"-home","aria-hidden":!1,"aria-labelled-by":"plyr-settings-toggle-"+e.id,role:"tabpanel"}),y=r.createElement("ul",{role:"tablist"});["captions","quality","speed","loop"].forEach(function(t){var n=r.createElement("li",{role:"tab"}),s=r.createElement("button",r.extend(r.getAttributesFromSelector(Ve.selectors.buttons.settings),{type:"button",class:Ve.classes.control+" "+Ve.classes.control+"--forward",id:"plyr-settings-"+e.id+"-"+t+"-tab","aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id+"-"+t,"aria-expanded":!1}),Ve.i18n[t]),a=r.createElement("span",{class:Ve.classes.menu.value});a.innerHTML=e[t],s.appendChild(a),n.appendChild(s),y.appendChild(n),Oe.elements.settings.tabs[t]=n}),f.appendChild(y),m.appendChild(f),["captions","quality","speed","loop"].forEach(function(t){var n=r.createElement("div",{id:"plyr-settings-"+e.id+"-"+t,"aria-hidden":!0,"aria-labelled-by":"plyr-settings-"+e.id+"-"+t+"-tab",role:"tabpanel",tabindex:-1}),s=r.createElement("button",{type:"button",class:Ve.classes.control+" "+Ve.classes.control+"--back","aria-haspopup":!0,"aria-controls":"plyr-settings-"+e.id+"-home","aria-expanded":!1},Ve.i18n[t]);n.appendChild(s);var a=r.createElement("ul");n.appendChild(a),m.appendChild(n),Oe.elements.settings.panes[t]=n}),p.appendChild(m),d.appendChild(p),t.appendChild(d),Oe.elements.settings.menu=d}return r.inArray(Ve.controls,"pip")&&o.pip&&t.appendChild(k("pip")),r.inArray(Ve.controls,"airplay")&&o.airplay&&t.appendChild(k("airplay")),r.inArray(Ve.controls,"fullscreen")&&t.appendChild(k("fullscreen")),Oe.elements.controls=t,T(),F(),t}function S(e,n){function s(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?h(t):null}function a(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"}}var l=Oe.elements.settings.panes.quality.querySelector("ul");r.emptyElement(l),r.is.array(e)&&!r.is.empty(e)&&e.filter(function(e){return!r.inArray(["tiny","small"],e)}).forEach(function(e){var n=r.createElement("li"),i=r.createElement("label",{class:Ve.classes.control,for:"plyr-quality-"+e}),o=r.createElement("input",r.extend(r.getAttributesFromSelector(Ve.selectors.inputs.quality),{type:"radio",id:"plyr-quality-"+e,name:"plyr-quality",value:e}));e===Ve.quality.selected&&o.setAttribute("checked",""),i.appendChild(o),i.appendChild(t.createTextNode(a(e)));var u=s(e);r.is.htmlElement(u)&&i.appendChild(u),n.appendChild(i),l.appendChild(n)})}function T(){var e=["start","end","all","reset"],t=Oe.elements.settings.panes.loop.querySelector("ul");r.emptyElement(t),e.forEach(function(e){var n=r.createElement("li"),s=r.createElement("button",r.extend(r.getAttributesFromSelector(Ve.selectors.buttons.loop),{type:"button",class:Ve.classes.control,"data-plyr-loop-action":e}),Ve.i18n[e]);if(r.inArray(["start","end"],e)){var a=h("0:00");s.appendChild(a)}n.appendChild(s),t.appendChild(n)})}function x(){var e=Oe.elements.settings.panes.captions.querySelector("ul");if(r.emptyElement(e),!r.is.empty(Oe.captions.tracks)){var n=[].map.call(Oe.captions.tracks,function(e){return{language:e.language,badge:!0,label:r.is.empty(e.label)?e.language.toUpperCase():e.label}});n.unshift({language:"off",label:Ve.i18n.none}),n.forEach(function(n){var s=r.createElement("li"),a=r.createElement("label",{class:Ve.classes.control,for:"plyr-language-"+n.language}),l=r.createElement("input",r.extend(r.getAttributesFromSelector(Ve.selectors.inputs.language),{type:"radio",id:"plyr-language-"+n.language,name:"plyr-language",value:n.language}));n.language===Ve.captions.language.toLowerCase()&&l.setAttribute("checked",""),a.appendChild(l),a.appendChild(t.createTextNode(n.label||n.language)),n.badge&&a.appendChild(h(n.language.toUpperCase())),s.appendChild(a),e.appendChild(s)})}}function F(e){var t=Oe.elements.settings.panes.speed.querySelector("ul");r.emptyElement(t),r.is.array(e)||(e=Ve.speed.options),e.forEach(function(e){var n=r.createElement("li"),s=r.createElement("label",{class:Ve.classes.control,for:"plyr-speed-"+e.toString().replace(".","-")}),a=r.createElement("input",r.extend(r.getAttributesFromSelector(Ve.selectors.inputs.speed),{type:"radio",id:"plyr-speed-"+e.toString().replace(".","-"),name:"plyr-speed",value:e}));e===Ve.speed.selected&&a.setAttribute("checked",""),s.appendChild(a),s.insertAdjacentHTML("beforeend","×"+e),n.appendChild(s),t.appendChild(n)})}function I(){if(Oe.supported.full&&("audio"!==Oe.type||Ve.fullscreen.allowAudio)&&Ve.fullscreen.enabled){var e=o.fullscreen;e||Ve.fullscreen.fallback&&!r.inFrame()?(De((e?"Native":"Fallback")+" fullscreen enabled"),r.toggleClass(Oe.elements.container,Ve.classes.fullscreen.enabled,!0)):De("Fullscreen not supported and fallback disabled"),Oe.elements.buttons&&Oe.elements.buttons.fullscreen&&r.toggleState(Oe.elements.buttons.fullscreen,!1),f()}}function P(e){if(r.inArray(["video","vimeo"],Oe.type)&&("video"!==Oe.type||o.textTracks)&&(r.is.htmlElement(Oe.elements.captions)||(Oe.elements.captions=r.createElement("div",r.getAttributesFromSelector(Ve.selectors.captions)),r.insertAfter(Oe.elements.captions,Oe.elements.wrapper)),Oe.captions.tracks=r.is.array(e)?e:Oe.elements.media.textTracks,r.toggleClass(Oe.elements.container,Ve.classes.captions.enabled,!r.is.empty(Oe.captions.tracks)),!r.is.empty(Oe.captions.tracks))){if(O(),"video"===Oe.type){var t=Ve.captions.language.toLowerCase();[].forEach.call(Oe.captions.tracks,function(e){r.off(e,"cuechange",N),e.mode="hidden",e.language===t&&(Oe.captions.currentTrack=e)}),r.is.track(Oe.captions.currentTrack)||(Re("No language found to match "+t+" in tracks"),Oe.captions.currentTrack=Oe.captions.tracks[0]);var n=Oe.captions.currentTrack;r.is.track(n)&&r.inArray(["captions","subtitles"],n.kind)&&(r.on(n,"cuechange",N),n.activeCues&&n.activeCues.length>0&&N(n))}x()}}function _(){return!o.textTracks||r.is.empty(Oe.captions.tracks)?"No Subs":Oe.captions.enabled?Oe.captions.currentTrack.label:"Disabled"}function N(e){r.is.event(e)&&(e=e.target);var t=e.activeCues[0];r.is.cue(t)?q(t.getCueAsHTML()):q()}function L(e){r.is.string(e)?Ve.captions.language=e.toLowerCase():r.is.event(e)&&(Ve.captions.language=e.target.value.toLowerCase()),q(),P()}function q(e){if(r.is.htmlElement(Oe.elements.captions)){var t=r.createElement("span");r.emptyElement(Oe.elements.captions),r.is.undefined(e)&&(e=""),r.is.string(e)?t.textContent=e.trim():t.appendChild(e),Oe.elements.captions.appendChild(t)}else Re("No captions element to render to")}function O(){if(Oe.elements.buttons.captions){var e=Oe.storage.captions;r.is.boolean(e)?Ve.captions.active=e:e=Ve.captions.active,e&&(r.toggleClass(Oe.elements.container,Ve.classes.captions.active,!0),r.toggleState(Oe.elements.buttons.captions,!0))}}function j(e){Oe.supported.full&&Oe.elements.buttons.captions&&(r.is.boolean(e)||(e=Oe.elements.container.className.indexOf(Ve.classes.captions.active)===-1),Oe.captions.enabled=e,r.toggleState(Oe.elements.buttons.captions,Oe.captions.enabled),r.toggleClass(Oe.elements.container,Ve.classes.captions.active,Oe.captions.enabled),c(Oe.elements.container,Oe.captions.enabled?"captionsenabled":"captionsdisabled",!0),W({captions:Oe.captions.enabled}))}function M(){if(Ve.loadSprite){var e=g();e.absolute?(De("AJAX loading absolute SVG sprite"+(Oe.browser.isIE?" (due to IE)":"")),r.loadSprite(e.url,"sprite-plyr")):De("Sprite will be used as external resource directly")}r.inArray(Ve.controls,"play-large")&&(Oe.elements.buttons.playLarge=k("play-large"),Oe.elements.container.appendChild(Oe.elements.buttons.playLarge)),Oe.id=Math.floor(1e4*Math.random());var n=null;n=r.is.string(Ve.controls)?Ve.controls:r.is.function(Ve.controls)?Ve.controls({id:Oe.id,seektime:Ve.seekTime}):w({id:Oe.id,seektime:Ve.seekTime,speed:te(),quality:"HD",captions:_(),loop:"None"});var s;if(r.is.string(Ve.selectors.controls.container)&&(s=t.querySelector(Ve.selectors.controls.container)),r.is.htmlElement(s)||(s=Oe.elements.container),r.is.htmlElement(n)?s.appendChild(n):s.insertAdjacentHTML("beforeend",n),r.is.htmlElement(Oe.elements.controls)&&V(),Ve.tooltips.controls)for(var a=d([Ve.selectors.controls.wrapper," ",Ve.selectors.labels," .",Ve.classes.hidden].join("")),l=a.length-1;l>=0;l--){var i=a[l];r.toggleClass(i,Ve.classes.hidden,!1),r.toggleClass(i,Ve.classes.tooltip,!0)}}function V(){try{return Oe.elements.controls=p(Ve.selectors.controls.wrapper),Oe.elements.buttons={play:d(Ve.selectors.buttons.play),pause:p(Ve.selectors.buttons.pause),restart:p(Ve.selectors.buttons.restart),rewind:p(Ve.selectors.buttons.rewind),forward:p(Ve.selectors.buttons.forward),mute:p(Ve.selectors.buttons.mute),pip:p(Ve.selectors.buttons.pip),airplay:p(Ve.selectors.buttons.airplay),settings:p(Ve.selectors.buttons.settings),captions:p(Ve.selectors.buttons.captions),fullscreen:p(Ve.selectors.buttons.fullscreen)},Oe.elements.progress=p(Ve.selectors.progress),Oe.elements.inputs={seek:p(Ve.selectors.inputs.seek),volume:p(Ve.selectors.inputs.volume)},Oe.elements.display={buffer:p(Ve.selectors.display.buffer),played:p(Ve.selectors.display.played),volume:p(Ve.selectors.display.volume),duration:p(Ve.selectors.display.duration),currentTime:p(Ve.selectors.display.currentTime)},r.is.htmlElement(Oe.elements.progress)&&(Oe.elements.display.seekTooltip=Oe.elements.progress.querySelector("."+Ve.classes.tooltip)),!0}catch(e){return Re("It looks like there is a problem with your custom controls HTML",e),R(!0),!1}}function D(){r.toggleClass(Oe.elements.container,Ve.selectors.container.replace(".",""),Oe.supported.full)}function R(e){e&&r.inArray(l.html5,Oe.type)?Oe.elements.media.setAttribute("controls",""):Oe.elements.media.removeAttribute("controls")}function B(e){var t=Ve.i18n.play;if(r.is.string(Ve.title)&&!r.is.empty(Ve.title)&&(t+=", "+Ve.title,Oe.elements.container.setAttribute("aria-label",Ve.title)),Oe.supported.full&&(r.is.htmlElement(Oe.elements.buttons.play)&&Oe.elements.buttons.play.setAttribute("aria-label",t),r.is.htmlElement(Oe.elements.buttons.playLarge)&&Oe.elements.buttons.playLarge.setAttribute("aria-label",t)),r.is.htmlElement(e)){var n=r.is.string(Ve.title)&&!r.is.empty(Ve.title)?Ve.title:"video";e.setAttribute("title",Ve.i18n.frameTitle.replace("{title}",n))}}function H(){var t=null;Oe.storage={},o.storage&&Ve.storage.enabled&&(e.localStorage.removeItem("plyr-volume"),t=e.localStorage.getItem(Ve.storage.key),t&&(/^\d+(\.\d+)?$/.test(t)?W({volume:parseFloat(t)}):Oe.storage=JSON.parse(t)))}function W(t){o.storage&&Ve.storage.enabled&&(r.extend(Oe.storage,t),e.localStorage.setItem(Ve.storage.key,JSON.stringify(Oe.storage)))}function Y(){return Oe.elements.media?(Oe.supported.full&&(r.toggleClass(Oe.elements.container,Ve.classes.type.replace("{0}",Oe.type),!0),r.inArray(l.embed,Oe.type)&&r.toggleClass(Oe.elements.container,Ve.classes.type.replace("{0}","video"),!0),r.toggleClass(Oe.elements.container,Ve.classes.pip.enabled,o.pip&&"video"===Oe.type),r.toggleClass(Oe.elements.container,Ve.classes.airplay.enabled,o.airplay&&r.inArray(l.html5,Oe.type)),r.toggleClass(Oe.elements.container,Ve.classes.stopped,Ve.autoplay),r.toggleClass(Oe.elements.container,Ve.classes.isIos,Oe.browser.isIos),r.toggleClass(Oe.elements.container,Ve.classes.isTouch,o.touch)),r.inArray(["video","youtube","vimeo"],Oe.type)&&(Oe.elements.wrapper=r.createElement("div",{class:Ve.classes.videoWrapper}),r.wrap(Oe.elements.media,Oe.elements.wrapper)),void(r.inArray(l.embed,Oe.type)&&U())):void Re("No media element found!")}function U(){var t,n=Oe.type+"-"+Math.floor(1e4*Math.random());switch(Oe.type){case"youtube":t=r.parseYouTubeId(Oe.embedId);break;default:t=Oe.embedId}for(var s=d('[id^="'+Oe.type+'-"]'),a=s.length-1;a>=0;a--)r.removeElement(s[a]);if(r.toggleClass(Oe.elements.wrapper,Ve.classes.embedWrapper,!0),"youtube"===Oe.type)Oe.elements.media.setAttribute("id",n),r.is.object(e.YT)?J(t):(r.injectScript(Ve.urls.youtube.api),e.onYouTubeReadyCallbacks=e.onYouTubeReadyCallbacks||[],e.onYouTubeReadyCallbacks.push(function(){J(t)}),e.onYouTubeIframeAPIReady=function(){e.onYouTubeReadyCallbacks.forEach(function(e){e()})});else if("vimeo"===Oe.type)if(Oe.elements.media.setAttribute("id",n),r.is.object(e.Vimeo))X(t);else{r.injectScript(Ve.urls.vimeo.api);var l=e.setInterval(function(){r.is.object(e.Vimeo)&&(e.clearInterval(l),X(t))},50)}else if("soundcloud"===Oe.type){var i=r.createElement("iframe");i.loaded=!1,r.on(i,"load",function(){i.loaded=!0}),r.setAttributes(i,{src:"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/"+t,id:n}),Oe.elements.media.appendChild(i),e.SC||r.injectScript(Ve.urls.soundcloud.api);var o=e.setInterval(function(){e.SC&&i.loaded&&(e.clearInterval(o),z.call(i))},50)}}function Q(){Oe.supported.full&&(Ne(),Le()),B(p("iframe"))}function J(t){Oe.embed=new e.YT.Player(Oe.elements.media.id,{videoId:t,playerVars:{autoplay:Ve.autoplay?1:0,controls:Oe.supported.full?0:1,rel:0,showinfo:0,iv_load_policy:3,cc_load_policy:Ve.captions.active?1:0,cc_lang_pref:"en",wmode:"transparent",modestbranding:1,disablekb:1,playsinline:1,origin:e.location.href},events:{onError:function(e){c(Oe.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;Oe.elements.media.play=function(){n.playVideo(),Oe.elements.media.paused=!1},Oe.elements.media.pause=function(){n.pauseVideo(),Oe.elements.media.paused=!0},Oe.elements.media.stop=function(){n.stopVideo(),Oe.elements.media.paused=!0},Oe.elements.media.duration=n.getDuration(),Oe.elements.media.paused=!0,Oe.elements.media.currentTime=0,Oe.elements.media.muted=n.isMuted();var s=n.getPlaybackRate(),a=n.getAvailablePlaybackRates();console.warn(s,a),Ve.title=n.getVideoData().title,Oe.supported.full&&Oe.elements.media.setAttribute("tabindex",-1),Q(),c(Oe.elements.media,"timeupdate"),c(Oe.elements.media,"durationchange"),e.clearInterval(je.buffering),je.buffering=e.setInterval(function(){Oe.elements.media.buffered=n.getVideoLoadedFraction(),(null===Oe.elements.media.lastBuffered||Oe.elements.media.lastBuffered<Oe.elements.media.buffered)&&c(Oe.elements.media,"progress"),Oe.elements.media.lastBuffered=Oe.elements.media.buffered,1===Oe.elements.media.buffered&&(e.clearInterval(je.buffering),c(Oe.elements.media,"canplaythrough"))},200)},onStateChange:function(t){var n=t.target;switch(e.clearInterval(je.playing),t.data){case 0:if(Ve.loop.active){n.stopVideo(),n.playVideo();break}Oe.elements.media.paused=!0,c(Oe.elements.media,"ended");break;case 1:Oe.elements.media.paused=!1,Oe.elements.media.seeking&&c(Oe.elements.media,"seeked"),Oe.elements.media.seeking=!1,c(Oe.elements.media,"play"),c(Oe.elements.media,"playing"),je.playing=e.setInterval(function(){Oe.elements.media.currentTime=n.getCurrentTime(),c(Oe.elements.media,"timeupdate")},100),Oe.elements.media.duration!==n.getDuration()&&(Oe.elements.media.duration=n.getDuration(),c(Oe.elements.media,"durationchange"));var s=n.getAvailableQualityLevels(),a=n.getPlaybackQuality();S(s,a);break;case 2:Oe.elements.media.paused=!0,c(Oe.elements.media,"pause")}c(Oe.elements.container,"statechange",!1,{code:t.data})}}})}function X(t){Oe.embed=new e.Vimeo.Player(Oe.elements.media,{id:t,loop:Ve.loop.active,autoplay:Ve.autoplay,byline:!1,portrait:!1,title:!1}),Oe.elements.media.play=function(){Oe.embed.play(),Oe.elements.media.paused=!1},Oe.elements.media.pause=function(){Oe.embed.pause(),Oe.elements.media.paused=!0},Oe.elements.media.stop=function(){Oe.embed.stop(),Oe.elements.media.paused=!0},Oe.elements.media.paused=!0,Oe.elements.media.currentTime=0,Q(),Oe.embed.getCurrentTime().then(function(e){Oe.elements.media.currentTime=e,c(Oe.elements.media,"timeupdate")}),Oe.embed.getDuration().then(function(e){Oe.elements.media.duration=e,c(Oe.elements.media,"durationchange")}),Oe.embed.getTextTracks().then(function(e){P(e),Ve.captions.active&&Oe.embed.enableTextTrack(Ve.captions.language.toLowerCase())}),Oe.embed.on("cuechange",function(e){var t=null;e.cues.length&&(t=r.stripHTML(e.cues[0].text)),q(t)}),Oe.embed.on("loaded",function(){r.is.htmlElement(Oe.embed.element)&&Oe.supported.full&&Oe.embed.element.setAttribute("tabindex",-1)}),Oe.embed.on("play",function(){Oe.elements.media.paused=!1,c(Oe.elements.media,"play"),c(Oe.elements.media,"playing")}),Oe.embed.on("pause",function(){Oe.elements.media.paused=!0,c(Oe.elements.media,"pause")}),Oe.embed.on("timeupdate",function(e){Oe.elements.media.seeking=!1,Oe.elements.media.currentTime=e.seconds,c(Oe.elements.media,"timeupdate")}),Oe.embed.on("progress",function(e){Oe.elements.media.buffered=e.percent,c(Oe.elements.media,"progress"),1===parseInt(e.percent)&&c(Oe.elements.media,"canplaythrough")}),Oe.embed.on("seeked",function(){Oe.elements.media.seeking=!1,c(Oe.elements.media,"seeked"),c(Oe.elements.media,"play")}),Oe.embed.on("ended",function(){Oe.elements.media.paused=!0,c(Oe.elements.media,"ended")})}function z(){Oe.embed=e.SC.Widget(this),Oe.embed.bind(e.SC.Widget.Events.READY,function(){Oe.elements.media.play=function(){Oe.embed.play(),Oe.elements.media.paused=!1},Oe.elements.media.pause=function(){Oe.embed.pause(),Oe.elements.media.paused=!0},Oe.elements.media.stop=function(){Oe.embed.seekTo(0),Oe.embed.pause(),Oe.elements.media.paused=!0},Oe.elements.media.paused=!0,Oe.elements.media.currentTime=0,Oe.embed.getDuration(function(e){Oe.elements.media.duration=e/1e3,Q()}),Oe.embed.getPosition(function(e){Oe.elements.media.currentTime=e,c(Oe.elements.media,"timeupdate")}),Oe.embed.bind(e.SC.Widget.Events.PLAY,function(){Oe.elements.media.paused=!1,c(Oe.elements.media,"play"),c(Oe.elements.media,"playing")}),Oe.embed.bind(e.SC.Widget.Events.PAUSE,function(){Oe.elements.media.paused=!0,c(Oe.elements.media,"pause")}),Oe.embed.bind(e.SC.Widget.Events.PLAY_PROGRESS,function(e){Oe.elements.media.seeking=!1,Oe.elements.media.currentTime=e.currentPosition/1e3,c(Oe.elements.media,"timeupdate")}),Oe.embed.bind(e.SC.Widget.Events.LOAD_PROGRESS,function(e){Oe.elements.media.buffered=e.loadProgress,c(Oe.elements.media,"progress"),1===parseInt(e.loadProgress)&&c(Oe.elements.media,"canplaythrough")}),Oe.embed.bind(e.SC.Widget.Events.FINISH,function(){Oe.elements.media.paused=!0,c(Oe.elements.media,"ended")})})}function $(){"play"in Oe.elements.media&&Oe.elements.media.play()}function G(){"pause"in Oe.elements.media&&Oe.elements.media.pause()}function K(e){return r.is.boolean(e)||(e=Oe.elements.media.paused),e?$():G(),e}function Z(e){r.inArray(["start","end","all","none","toggle"],e)||(e="toggle");var n=Number(Oe.elements.media.currentTime);switch(e){case"start":Ve.loop.end&&Ve.loop.end<=n&&(Ve.loop.end=null),Ve.loop.start=n,Ve.loop.indicator.start=Oe.elements.display.played.value;break;case"end":if(Ve.loop.start>=n)return;Ve.loop.end=n,Ve.loop.indicator.end=Oe.elements.display.played.value;break;case"all":Ve.loop.start=0,Ve.loop.end=Oe.elements.media.duration-2,Ve.loop.indicator.start=0,Ve.loop.indicator.end=100;break;case"toggle":Ve.loop.active?(Ve.loop.start=0,Ve.loop.end=null):(Ve.loop.start=0,Ve.loop.end=Oe.elements.media.duration-2);break;default:Ve.loop.start=0,Ve.loop.end=null}Ve.loop.active=r.is.number(Ve.loop.start)&&r.is.number(Ve.loop.end);var s=(he(Ve.loop.start,p('[data-plyr-loop="start"]')),null);r.is.number(Ve.loop.end)&&(s=he(Ve.loop.end,t.querySelector('[data-loop__value="loopout"]'))),Ve.loop.active}function ee(e){return r.is.event(e)?e=parseFloat(e.target.value):r.is.number(e)||(e=parseFloat(Oe.storage.speed||Ve.speed.selected)),e<.1&&(e=.1),e>2&&(e=2),r.is.array(Ve.speed.options)?(Ve.speed.selected=e,Oe.elements.media.playbackRate=e,void W({speed:e})):void Re("Invalid speeds format")}function te(){return Ve.speed.selected.toFixed(1).toString().replace(".0","")+"×"}function ne(e){r.is.number(e)||(e=Ve.seekTime),ae(Oe.elements.media.currentTime-e)}function se(e){r.is.number(e)||(e=Ve.seekTime),ae(Oe.elements.media.currentTime+e)}function ae(e){var t=0,n=Oe.elements.media.paused,s=le();r.is.number(e)?t=e:r.is.event(e)&&r.inArray(["input","change"],e.type)&&(t=e.target.value/e.target.max*s),t<0?t=0:t>s&&(t=s),Ee(t);try{Oe.elements.media.currentTime=t.toFixed(4)}catch(e){}if(r.inArray(l.embed,Oe.type)){switch(Oe.type){case"youtube":Oe.embed.seekTo(t);break;case"vimeo":Oe.embed.setCurrentTime(t.toFixed(0));break;case"soundcloud":Oe.embed.seekTo(1e3*t)}n&&G(),c(Oe.elements.media,"timeupdate"),Oe.elements.media.seeking=!0,c(Oe.elements.media,"seeking")}De("Seeking to "+Oe.elements.media.currentTime+" seconds")}function le(){var e=parseInt(Ve.duration),t=0;return null===Oe.elements.media.duration||isNaN(Oe.elements.media.duration)||(t=Oe.elements.media.duration),isNaN(e)?t:e}function re(){r.toggleClass(Oe.elements.container,Ve.classes.playing,!Oe.elements.media.paused),r.toggleClass(Oe.elements.container,Ve.classes.stopped,Oe.elements.media.paused),we(Oe.elements.media.paused)}function ie(){s={x:e.pageXOffset||0,y:e.pageYOffset||0}}function oe(){e.scrollTo(s.x,s.y)}function ue(e){var n=o.fullscreen;if(n){if(!e||e.type!==i.eventType)return i.isFullScreen(Oe.elements.container)?i.cancelFullScreen():(ie(),i.requestFullScreen(Oe.elements.container)),void(Oe.fullscreen.active=i.isFullScreen(Oe.elements.container));Oe.fullscreen.active=i.isFullScreen(Oe.elements.container)}else Oe.fullscreen.active=!Oe.fullscreen.active,t.body.style.overflow=Oe.fullscreen.active?"hidden":"";r.toggleClass(Oe.elements.container,Ve.classes.fullscreen.active,Oe.fullscreen.active),f(Oe.fullscreen.active),Oe.elements.buttons&&Oe.elements.buttons.fullscreen&&r.toggleState(Oe.elements.buttons.fullscreen,Oe.fullscreen.active),c(Oe.elements.container,Oe.fullscreen.active?"enterfullscreen":"exitfullscreen",!0),!Oe.fullscreen.active&&n&&oe()}function ce(n){var s=Oe.elements.settings.menu.parentNode,a=n.target,l=t.getElementById(a.getAttribute("aria-controls")),i="false"===a.getAttribute("aria-expanded");if(r.is.htmlElement(l)){var o,u,c,d="tabpanel"===l.getAttribute("role");if(d){var p=s.querySelector('[role="tabpanel"][aria-hidden="false"]');c=p.parentNode,[].forEach.call(s.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.removeElement(m)}l.setAttribute("aria-hidden",!i),a.setAttribute("aria-expanded",i),l.removeAttribute("tabindex"),d&&(c.style.width=o+"px",c.style.height=u+"px",e.setTimeout(function(){c.style.width="",c.style.height=""},300))}}function de(e){if(r.is.boolean(e)||(e=!Oe.elements.media.muted),r.toggleState(Oe.elements.buttons.mute,e),Oe.elements.media.muted=e,0===Oe.elements.media.volume&&pe(Ve.volume),r.inArray(l.embed,Oe.type)){switch(Oe.type){case"youtube":Oe.embed[Oe.elements.media.muted?"mute":"unMute"]();break;case"vimeo":case"soundcloud":Oe.embed.setVolume(Oe.elements.media.muted?0:parseFloat(Ve.volume/10))}c(Oe.elements.media,"volumechange")}}function pe(e){var t=10,n=0;if(r.is.event(e)&&(e=e.target.value),r.is.undefined(e)&&(e=Oe.storage.volume),(null===e||isNaN(e))&&(e=Ve.volume),e>t&&(e=t),e<n&&(e=n),Oe.elements.media.volume=parseFloat(e/t),Oe.elements.display.volume&&(Oe.elements.display.volume.value=e),r.inArray(l.embed,Oe.type)){switch(Oe.type){case"youtube":Oe.embed.setVolume(100*Oe.elements.media.volume);break;case"vimeo":case"soundcloud":Oe.embed.setVolume(Oe.elements.media.volume)}c(Oe.elements.media,"volumechange")}0===e?Oe.elements.media.muted=!0:Oe.elements.media.muted&&e>0&&de()}function me(e){var t=Oe.elements.media.muted?0:10*Oe.elements.media.volume;r.is.number(e)||(e=1),pe(t+e)}function fe(e){var t=Oe.elements.media.muted?0:10*Oe.elements.media.volume;r.is.number(e)||(e=1),pe(t-e)}function ye(){var e=Oe.elements.media.muted?0:10*Oe.elements.media.volume;Oe.supported.full&&(Oe.elements.inputs.volume&&(Oe.elements.inputs.volume.value=e),Oe.elements.display.volume&&(Oe.elements.display.volume.value=e)),W({volume:e}),r.toggleClass(Oe.elements.container,Ve.classes.muted,0===e),Oe.supported.full&&Oe.elements.buttons.mute&&r.toggleState(Oe.elements.buttons.mute,0===e)}function ge(e){var t="waiting"===e.type;clearTimeout(je.loading),je.loading=setTimeout(function(){r.toggleClass(Oe.elements.container,Ve.classes.loading,t),we(t)},t?250:0)}function be(e){if(Oe.supported.full){var t=Oe.elements.display.played,n=0,s=le();if(e)switch(e.type){case"timeupdate":case"seeking":if(Oe.elements.controls.pressed)return;n=r.getPercentage(Oe.elements.media.currentTime,s),"timeupdate"===e.type&&Oe.elements.inputs.seek&&(Oe.elements.inputs.seek.value=n);break;case"playing":case"progress":t=Oe.elements.display.buffer,n=function(){var e=Oe.elements.media.buffered;return e&&e.length?r.getPercentage(e.end(0),s):r.is.number(e)?100*e:0}()}r.is.number(Ve.loop.start)&&r.is.number(Ve.loop.end)&&Oe.elements.media.currentTime>=Ve.loop.end&&ae(Ve.loop.start),ve(t,n)}}function ve(e,t){if(Oe.supported.full){if(r.is.undefined(t)&&(t=0),r.is.undefined(e)){if(!r.is.htmlElement(Oe.elements.display.buffer))return;e=Oe.elements.display.buffer}if(r.is.htmlElement(e)){e.value=t;var n=e.getElementsByTagName("span")[0];r.is.htmlElement(n)&&(n.childNodes[0].nodeValue=t)}}}function he(e,t){if(t){isNaN(e)&&(e=0);var n=parseInt(e%60),s=parseInt(e/60%60),a=parseInt(e/60/60%60),l=parseInt(le()/60/60%60)>0;n=("0"+n).slice(-2),s=("0"+s).slice(-2);var r=(l?a+":":"")+s+":"+n;return t.textContent=r,r}}function ke(){if(Oe.supported.full){var e=le()||0;!Oe.elements.display.duration&&Ve.displayDuration&&Oe.elements.media.paused&&he(e,Oe.elements.display.currentTime),Oe.elements.display.duration&&he(e,Oe.elements.display.duration),Ae()}}function Ce(e){he(Oe.elements.media.currentTime,Oe.elements.display.currentTime),e&&"timeupdate"===e.type&&Oe.elements.media.seeking||be(e)}function Ee(e){r.is.number(e)||(e=0);var t=le(),n=r.getPercentage(e,t);Oe.elements.progress&&Oe.elements.display.played&&(Oe.elements.display.played.value=n),Oe.elements.buttons&&Oe.elements.inputs.seek&&(Oe.elements.inputs.seek.value=n)}function Ae(e){var t=le();if(Ve.tooltips.seek&&r.is.htmlElement(Oe.elements.inputs.seek)&&r.is.htmlElement(Oe.elements.display.seekTooltip)&&0!==t){var n=Oe.elements.inputs.seek.getBoundingClientRect(),s=0,a=Ve.classes.tooltip+"--visible";if(r.is.event(e))s=100/n.width*(e.pageX-n.left);else{if(!r.hasClass(Oe.elements.display.seekTooltip,a))return;s=Oe.elements.display.seekTooltip.style.left.replace("%",""); +}s<0?s=0:s>100&&(s=100),he(t/100*s,Oe.elements.display.seekTooltip),Oe.elements.display.seekTooltip.style.left=s+"%",r.is.event(e)&&r.inArray(["mouseenter","mouseleave"],e.type)&&r.toggleClass(Oe.elements.display.seekTooltip,a,"mouseenter"===e.type)}}function we(t){if(Ve.hideControls&&"audio"!==Oe.type){var n=0,s=!1,a=t,l=r.hasClass(Oe.elements.container,Ve.classes.loading);if(r.is.boolean(t)||(t&&t.type?(s="enterfullscreen"===t.type,a=r.inArray(["mousemove","touchstart","mouseenter","focus"],t.type),r.inArray(["mousemove","touchmove"],t.type)&&(n=2e3),"focus"===t.type&&(n=3e3)):a=r.hasClass(Oe.elements.container,Ve.classes.hideControls)),e.clearTimeout(je.hover),a||Oe.elements.media.paused||l){if(r.toggleClass(Oe.elements.container,Ve.classes.hideControls,!1),Oe.elements.media.paused||l)return;o.touch&&(n=3e3)}a&&Oe.elements.media.paused||(je.hover=e.setTimeout(function(){(!Oe.elements.controls.pressed&&!Oe.elements.controls.hover||s)&&r.toggleClass(Oe.elements.container,Ve.classes.hideControls,!0)},n))}}function Se(e){if(!r.is.undefined(e))return void Te(e);var t;switch(Oe.type){case"youtube":t=Oe.embed.getVideoUrl();break;case"vimeo":Oe.embed.getVideoUrl.then(function(e){t=e});break;case"soundcloud":Oe.embed.getCurrentSound(function(e){t=e.permalink_url});break;default:t=Oe.elements.media.currentSrc}return t||""}function Te(e){function t(){if(Oe.embed=null,m("media"),m("captions"),m("wrapper"),Oe.elements.container&&Oe.elements.container.removeAttribute("class"),"type"in e&&(Oe.type=e.type,"video"===Oe.type)){var t=e.sources[0];"type"in t&&r.inArray(l.embed,t.type)&&(Oe.type=t.type)}switch(Oe.supported=r.checkSupport(Oe.type,Ve.inline),Oe.type){case"video":Oe.elements.media=r.createElement("video");break;case"audio":Oe.elements.media=r.createElement("audio");break;case"youtube":case"vimeo":case"soundcloud":Oe.elements.media=r.createElement("div"),Oe.embedId=e.sources[0].src}r.prependChild(Oe.elements.container,Oe.elements.media),r.is.boolean(e.autoplay)&&(Ve.autoplay=e.autoplay),r.inArray(l.html5,Oe.type)&&(Ve.crossorigin&&Oe.elements.media.setAttribute("crossorigin",""),Ve.autoplay&&Oe.elements.media.setAttribute("autoplay",""),"poster"in e&&Oe.elements.media.setAttribute("poster",e.poster),Ve.loop.active&&Oe.elements.media.setAttribute("loop",""),Ve.inline&&Oe.elements.media.setAttribute("playsinline","")),r.toggleClass(Oe.elements.container,Ve.classes.fullscreen.active,Oe.fullscreen.active),r.toggleClass(Oe.elements.container,Ve.classes.captions.active,Oe.captions.enabled),D(),r.inArray(l.html5,Oe.type)&&y("source",e.sources),Y(),r.inArray(l.html5,Oe.type)&&("tracks"in e&&y("track",e.tracks),Oe.elements.media.load()),(r.inArray(l.html5,Oe.type)||r.inArray(l.embed,Oe.type)&&!Oe.supported.full)&&(Ne(),Le()),Ve.title=e.title,B()}return r.is.object(e)&&"sources"in e&&e.sources.length?(r.toggleClass(Oe.elements.container,Ve.classes.ready,!1),G(),Ee(),ve(),Pe(),void _e(t,!1)):void Re("Invalid source format")}function xe(e){"video"===Oe.type&&Oe.elements.media.setAttribute("poster",e)}function Fe(){function n(){var e=K(),t=Oe.elements.buttons[e?"play":"pause"],n=Oe.elements.buttons[e?"pause":"play"];if(n){var s=r.hasClass(t,Ve.classes.tabFocus);setTimeout(function(){r.is.htmlElement(n)&&n.focus(),s&&(r.toggleClass(t,Ve.classes.tabFocus,!1),r.toggleClass(n,Ve.classes.tabFocus,!0))},100)}}function s(e){return e.keyCode?e.keyCode:e.which}function a(e){r.toggleClass(d("."+Ve.classes.tabFocus),Ve.classes.tabFocus,!1),Oe.elements.container.contains(e)&&r.toggleClass(e,Ve.classes.tabFocus,!0)}function l(e){function t(){var e=Oe.elements.media.duration;r.is.number(e)&&ae(e/10*(a-48))}var a=s(e),l="keydown"===e.type,i=l&&a===c;if(r.is.number(a))if(l){var u=[48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67,73,76,79],d=[38,40];if(r.inArray(d,a)){var p=r.getFocusElement();if(r.is.htmlElement(p)&&"radio"===r.getFocusElement().type)return}switch(r.inArray(u,a)&&(e.preventDefault(),e.stopPropagation()),a){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:i||t();break;case 32:case 75:i||n();break;case 38:me();break;case 40:fe();break;case 77:i||de();break;case 39:se();break;case 37:ne();break;case 70:ue();break;case 67:i||j();break;case 73:Z("start");break;case 76:Z();break;case 79:Z("end")}!o.fullscreen&&Oe.fullscreen.active&&27===a&&ue(),c=a}else c=null}var u=Oe.browser.isIE?"change":"input";if(Ve.keyboardShortcuts.focused){var c=null;Ve.keyboardShortcuts.global&&r.on(e,"keydown keyup",function(e){var t=s(e),n=r.getFocusElement(),a=[48,49,50,51,52,53,54,56,57,75,77,70,67,73,76,79];!r.inArray(a,t)||r.is.htmlElement(n)&&r.matches(n,Ve.selectors.editable)||l(e)},!1),r.on(Oe.elements.container,"keydown keyup",l,!1)}r.on(e,"keyup",function(e){var t=s(e),n=r.getFocusElement();9===t&&a(n)}),r.on(t.body,"click",function(){r.toggleClass(p("."+Ve.classes.tabFocus),Ve.classes.tabFocus,!1)});for(var m in Oe.elements.buttons){var f=Oe.elements.buttons[m];r.on(f,"blur",function(){r.toggleClass(f,"tab-focus",!1)})}var y=function(e,t,n){r.is.function(t)&&t.call(this,e),r.is.function(n)&&n.call(this,e)};r.proxy(Oe.elements.buttons.play,"click",Ve.listeners.play,n),r.proxy(Oe.elements.buttons.playLarge,"click",Ve.listeners.play,n),r.proxy(Oe.elements.buttons.pause,"click",Ve.listeners.pause,n),r.proxy(Oe.elements.buttons.restart,"click",Ve.listeners.restart,ae),r.proxy(Oe.elements.buttons.rewind,"click",Ve.listeners.rewind,ne),r.proxy(Oe.elements.buttons.forward,"click",Ve.listeners.forward,se),r.proxy(Oe.elements.buttons.mute,"click",Ve.listeners.mute,de),r.proxy(Oe.elements.buttons.captions,"click",Ve.listeners.captions,j),r.proxy(Oe.elements.buttons.fullscreen,"click",Ve.listeners.fullscreen,ue),r.proxy(Oe.elements.buttons.pip,"click",Ve.listeners.pip,function(e){o.pip&&Oe.elements.media.webkitSetPresentationMode("picture-in-picture"===Oe.elements.media.webkitPresentationMode?"inline":"picture-in-picture")}),r.proxy(Oe.elements.buttons.airplay,"click",Ve.listeners.airplay,function(e){o.airplay&&Oe.elements.media.webkitShowPlaybackTargetPicker()}),r.on(Oe.elements.settings.menu,"click",ce),r.on(t.body,"click",function(e){var t=Oe.elements.settings.menu,n=t.querySelector("form");"true"===n.getAttribute("aria-hidden")||t.contains(e.target)||n.setAttribute("aria-hidden",!0)}),r.on(Oe.elements.settings.menu,"click",function(e){r.matches(e.target,Ve.selectors.inputs.language)?y.call(this,e,Ve.listeners.language,L):r.matches(e.target,Ve.selectors.inputs.quality)?y.call(this,e,Ve.listeners.quality,function(){Re("Set quality")}):r.matches(e.target,Ve.selectors.inputs.speed)?y.call(this,e,Ve.listeners.speed,ee):r.matches(e.target,Ve.selectors.buttons.loop)&&y.call(this,e,Ve.listeners.loop,function(){var t=e.target.getAttribute("data-loop__value")||e.target.getAttribute("data-loop__type");r.inArray(["start","end","all","none"],t)&&Z(t)})}),r.proxy(Oe.elements.inputs.seek,u,Ve.listeners.seek,ae),r.proxy(Oe.elements.inputs.volume,u,Ve.listeners.volume,pe),r.on(Oe.elements.progress,"mouseenter mouseleave mousemove",Ae),Ve.hideControls&&(r.on(Oe.elements.container,"mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen",we),r.on(Oe.elements.controls,"mouseenter mouseleave",function(e){Oe.elements.controls.hover="mouseenter"===e.type}),r.on(Oe.elements.controls,"mousedown mouseup touchstart touchend touchcancel",function(e){Oe.elements.controls.pressed=r.inArray(["mousedown","touchstart"],e.type)}),r.on(Oe.elements.controls,"focus blur",we,!0,!0)),r.proxy(Oe.elements.inputs.volume,"wheel",Ve.listeners.volume,function(e){var t=e.webkitDirectionInvertedFromDevice,n=.2,s=0;(e.deltaY<0||e.deltaX>0)&&(t?(fe(n),s=-1):(me(n),s=1)),(e.deltaY>0||e.deltaX<0)&&(t?(me(n),s=1):(fe(n),s=-1)),(1===s&&Oe.elements.media.volume<1||s===-1&&Oe.elements.media.volume>0)&&e.preventDefault()},!1),o.fullscreen&&r.on(t,i.eventType,ue)}function Ie(){if(r.on(Oe.elements.media,"timeupdate seeking",Ce),r.on(Oe.elements.media,"durationchange loadedmetadata",ke),r.on(Oe.elements.media,"ended",function(){"video"===Oe.type&&Ve.showPosterOnEnd&&("video"===Oe.type&&q(),ae(),Oe.elements.media.load())}),r.on(Oe.elements.media,"progress playing",be),r.on(Oe.elements.media,"volumechange",ye),r.on(Oe.elements.media,"play pause ended",re),r.on(Oe.elements.media,"waiting canplay seeked",ge),Ve.clickToPlay&&"audio"!==Oe.type){var e=p("."+Ve.classes.videoWrapper);if(!e)return;e.style.cursor="pointer",r.on(e,"click",function(){Ve.hideControls&&o.touch&&!Oe.elements.media.paused||(Oe.elements.media.paused?$():Oe.elements.media.ended?(ae(),$()):G())})}Ve.disableContextMenu&&r.on(Oe.elements.media,"contextmenu",function(e){e.preventDefault()},!1),r.on(Oe.elements.media,Ve.events.concat(["keyup","keydown"]).join(" "),function(e){c(Oe.elements.container,e.type,!0)})}function Pe(){if(r.inArray(l.html5,Oe.type)){for(var e=Oe.elements.media.querySelectorAll("source"),t=0;t<e.length;t++)r.removeElement(e[t]);Oe.elements.media.setAttribute("src","https://cdn.selz.com/plyr/blank.mp4"),Oe.elements.media.load(),De("Cancelled network requests")}}function _e(n,s){function a(){r.is.boolean(s)||(s=!0),r.is.function(n)&&n.call(Oe.original),s&&(Oe.elements.container.parentNode.replaceChild(Oe.original,Oe.elements.container),t.body.style.overflow="",c(Oe.original,"destroyed",!0))}switch(Oe.type){case"youtube":e.clearInterval(je.buffering),e.clearInterval(je.playing),Oe.embed.destroy(),a();break;case"vimeo":Oe.embed.unload().then(a),e.setTimeout(a,200);break;case"video":case"audio":R(!0),a()}}function Ne(){return Oe.supported.full?(r.is.htmlElement(Oe.elements.controls)||(M(),Fe()),void(r.is.htmlElement(Oe.elements.controls)&&(Ie(),R(),I(),P(),pe(),ye(),ee(),Z(),Ce(),re()))):(Re("Basic support only",Oe.type),m("controls"),m("buttons.play"),void R(!0))}function Le(){c(Oe.elements.container,"ready",!0),Ve.autoplay&&$()}function qe(e){if(!r.is.htmlElement(e))return Be("Setup failed. No suitable element passed."),!1;if(!Ve.enabled)return!1;if(!r.checkSupport().basic)return!1;if(e.plyr)return!1;var t=e.tagName.toLowerCase();switch(t){case"div":if(Oe.type=e.getAttribute("data-type"),Oe.embedId=e.getAttribute("data-video-id"),r.is.empty(Oe.type)||r.is.empty(Oe.embedId))return!1;e.removeAttribute("data-type"),e.removeAttribute("data-video-id");break;case"iframe":break;case"video":case"audio":Oe.type=t,Ve.crossorigin=null!==e.getAttribute("crossorigin"),Ve.autoplay=Ve.autoplay||null!==e.getAttribute("autoplay"),Ve.inline=null!==e.getAttribute("playsinline"),Ve.loop.active=Ve.loop||null!==e.getAttribute("loop");break;default:return!1}if(Oe.browser=r.getBrowser(),H(),Oe.supported=r.checkSupport(Oe.type,Ve.inline),!Oe.supported.basic)return!1;if(Oe.elements.container=r.wrap(e,r.createElement("div")),Oe.original=e.cloneNode(!0),Oe.elements.container.setAttribute("tabindex",0),D(),De(Oe.browser.name+" "+Oe.browser.version),Y(),Ve.debug){var n=Ve.events.concat(["setup","statechange","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled"]);r.on(Oe.elements.container,n.join(" "),function(e){De(["event:",e.type].join(" ").trim())})}return(r.inArray(l.html5,Oe.type)||r.inArray(l.embed,Oe.type)&&!Oe.supported.full)&&(Ne(),Le(),B()),!0}var Oe=this,je={},Me={};r.is.string(n)&&(n=t.querySelectorAll(n)),(e.jQuery&&n instanceof jQuery||r.is.nodeList(n)||r.is.array(n))&&(n=n[0]);var Ve=r.extend({},a,u,function(){try{return JSON.parse(n.getAttribute("data-plyr"))}catch(e){}}());Oe.elements={container:null,buttons:{},display:{},progress:{},inputs:{},settings:{menu:null,panes:{},tabs:{}},media:n,captions:null},Oe.captions={enabled:!1,captions:[],tracks:[],currentTrack:null},Oe.fullscreen={active:!1};var De=function(){},Re=function(){},Be=function(){};return Ve.debug&&"console"in e&&(De=e.console.log,Re=e.console.warn,Be=e.console.error),De("Config",Ve),De("Support",o),Me={getOriginal:function(){return Oe.original},getContainer:function(){return Oe.elements.container},getEmbed:function(){return Oe.embed},getMedia:function(){return Oe.elements.media},getType:function(){return Oe.type},getDuration:le,getCurrentTime:function(){return Oe.elements.media.currentTime},getVolume:function(){return Oe.elements.media.volume},isMuted:function(){return Oe.elements.media.muted},isReady:function(){return r.hasClass(Oe.elements.container,Ve.classes.ready)},isLoading:function(){return r.hasClass(Oe.elements.container,Ve.classes.loading)},isPaused:function(){return Oe.elements.media.paused},isLooping:function(){return Ve.loop.active},on:function(e,t){return r.on(Oe.elements.container,e,t),this},play:$,pause:G,loop:Z,stop:function(){G(),ae()},restart:ae,rewind:ne,forward:se,seek:ae,source:Se,poster:xe,setVolume:pe,setSpeed:ee,togglePlay:K,toggleMute:de,toggleCaptions:j,toggleFullscreen:ue,toggleControls:we,setLanguage:L,isFullscreen:Oe.fullscreen.active,support:function(e){return o.mime(Oe,e)},destroy:_e},qe(Oe.elements.media)?Me:null}var s={x:0,y:0},a={enabled:!0,title:"",debug:!1,autoplay:!1,seekTime:10,volume:10,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:{default:"auto",selected:"auto"},loop:{active:!1,start:0,end:null,indicator:{start:0,end:0}},speed:{selected:1,options:[.25,.5,.75,1,1.25,1.5,2]},keyboardShortcuts:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},selectors:{editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',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"]',loop:'[data-plyr="loop"]'},inputs:{seek:'[data-plyr="seek"]',volume:'[data-plyr="volume"]',speed:'[data-plyr="speed"]',language:'[data-plyr="language"]',quality:'[data-plyr="quality"]'},display:{currentTime:".plyr__time--current",duration:".plyr__time--duration",buffer:".plyr__progress--buffer",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:{active:!1,language:e.navigator.language.split("-")[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",none:"None"},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:["ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied"],logPrefix:""},l={embed:["youtube","vimeo","soundcloud"],html5:["video","audio"]},r={is:{object:function(e){return null!==e&&"object"==typeof e&&e.constructor===Object},array:function(e){return null!==e&&Array.isArray(e)},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},cue:function(t){return null!==t&&(t instanceof e.TextTrackCue||t instanceof e.VTTCue)},track:function(t){return null!==t&&t instanceof e.TextTrack},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}},getBrowser:function(){var e,t,n,s=navigator.userAgent,a=navigator.appName,l=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10),i=!1,o=!1,u=!1,c=!1;return navigator.appVersion.indexOf("Windows NT")!==-1&&navigator.appVersion.indexOf("rv:11")!==-1?(i=!0,a="IE",l="11"):(t=s.indexOf("MSIE"))!==-1?(i=!0,a="IE",l=s.substring(t+5)):(t=s.indexOf("Chrome"))!==-1?(u=!0,a="Chrome",l=s.substring(t+7)):(t=s.indexOf("Safari"))!==-1?(c=!0,a="Safari",l=s.substring(t+7),(t=s.indexOf("Version"))!==-1&&(l=s.substring(t+8))):(t=s.indexOf("Firefox"))!==-1?(o=!0,a="Firefox",l=s.substring(t+8)):(e=s.lastIndexOf(" ")+1)<(t=s.lastIndexOf("/"))&&(a=s.substring(e,t),l=s.substring(t+1),a.toLowerCase()===a.toUpperCase()&&(a=navigator.appName)),(n=l.indexOf(";"))!==-1&&(l=l.substring(0,n)),(n=l.indexOf(" "))!==-1&&(l=l.substring(0,n)),r=parseInt(""+l,10),isNaN(r)&&(l=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),{name:a,version:r,isIE:i,isOldIE:i&&r<=9,isFirefox:o,isChrome:u,isSafari:c,isIPhone:/(iPhone|iPod)/gi.test(navigator.platform),isIos:/(iPad|iPhone|iPod)/gi.test(navigator.platform)}},checkSupport:function(e,t){var n=!1,s=!1,a=r.getBrowser(),l=a.isIPhone&&t&&o.inline;switch(e){case"video":n=o.video,s=n&&!a.isOldIE&&(!a.isIPhone||l);break;case"audio":n=o.audio,s=n&&!a.isOldIE;break;case"youtube":n=o.video,s=n&&!a.isOldIE&&(!a.isIPhone||l);break;case"vimeo":case"soundcloud":n=!0,s=!a.isOldIE&&!a.isIos;break;default:n=o.audio&&o.video,s=n&&!a.isOldIE}return{basic:n,full:s}},injectScript:function(e){if(!t.querySelectorAll('script[src="'+e+'"]').length){var n=t.createElement("script");n.src=e;var s=t.getElementsByTagName("script")[0];s.parentNode.insertBefore(n,s)}},inFrame:function(){try{return e.self!==e.top}catch(e){return!0}},inArray:function(e,t){return r.is.array(e)&&e.indexOf(t)!==-1},replaceAll:function(e,t,n){return e.replace(new RegExp(t.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g,"\\$1"),"g"),n)},wrap:function(e,t){e.length||(e=[e]);for(var n=e.length-1;n>=0;n--){var s=n>0?t.cloneNode(!0):t,a=e[n],l=a.parentNode,r=a.nextSibling;return s.appendChild(a),r?l.insertBefore(s,r):l.appendChild(s),s}},removeElement:function(e){r.is.htmlElement(e)&&r.is.htmlElement(e.parentNode)&&e.parentNode.removeChild(e)},prependChild:function(e,t){e.insertBefore(t,e.firstChild)},insertAfter:function(e,t){t.parentNode.insertBefore(e,t.nextSibling)},createElement:function(e,n,s){var a=t.createElement(e);return r.is.object(n)&&r.setAttributes(a,n),r.is.string(s)&&(a.textContent=s),a},insertElement:function(e,t,n,s){var a=r.createElement(e,n,s);r.prependChild(t,a)},emptyElement:function(e){for(var t=e.childNodes.length;t--;)e.removeChild(e.lastChild)},setAttributes:function(e,t){for(var n in t)e.setAttribute(n,t[n])},getAttributesFromSelector:function(e,t){if(!r.is.string(e)||r.is.empty(e))return{};var n={};return e.split(",").forEach(function(e){e=e.trim();var s=e.charAt(0);switch(s){case".":var a=e.replace(".","");r.is.object(t)&&r.is.string(t.class)&&(t.class+=" "+a),n.class=a;break;case"#":n.id=e.replace("#","");break;case"[":e=e.replace(/[\[\]]/g,"");var l=e.split("="),i=l[0],o=l.length>1?l[1].replace(/[\"\']/g,""):"";n[i]=o}}),n},toggleClass:function(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var s=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=s+(n?" "+t:"")}},hasClass:function(e,t){return!!e&&(e.classList?e.classList.contains(t):new RegExp("(\\s|^)"+t+"(\\s|$)").test(e.className))},matches:function(e,n){var s=Element.prototype,a=s.matches||s.webkitMatchesSelector||s.mozMatchesSelector||s.msMatchesSelector||function(e){return[].indexOf.call(t.querySelectorAll(e),this)!==-1};return a.call(e,n)},getFocusElement:function(){var e=t.activeElement;return e=e&&e!==t.body?t.querySelector(":focus"):null},proxy:function(e,t,n,s,a,l){r.on(e,t,function(t){n&&n.apply(e,[t]),s.apply(e,[t])},a,l)},toggleListener:function(e,t,n,s,a,l){if(t=t.split(" "),r.is.boolean(l)||(l=!1),r.is.boolean(a)||(a=!0),e instanceof NodeList){var i=1===arguments.length?[arguments[0]]:Array.apply(null,arguments);return i.shift(),void[].forEach.call(e,function(e){e instanceof Node&&r.toggleListener.apply(null,[e].concat(i))})}var u=l;o.passiveListeners&&(u={passive:a,capture:l}),t.forEach(function(t){e[s?"addEventListener":"removeEventListener"](t,n,u)})},on:function(e,t,n,s,a){r.is.undefined(e)||r.toggleListener(e,t,n,!0,s,a)},off:function(e,t,n,s,a){r.is.undefined(e)||r.toggleListener(e,t,n,!1,s,a)},event:function(n,s,a,l){if(n&&s){r.is.boolean(a)||(a=!1);var i;r.is.function(e.CustomEvent)?i=e.CustomEvent:(i=function(e,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var s=t.createEvent("CustomEvent");return s.initCustomEvent(e,n.bubbles,n.cancelable,n.detail),s},i.prototype=e.Event.prototype);var o=new i(s,{bubbles:a,detail:l});n.dispatchEvent(o)}},toggleState:function(e,t){if(e)return t=r.is.boolean(t)?t:!e.getAttribute("aria-pressed"),e.setAttribute("aria-pressed",t),t},getPercentage:function(e,t){return 0===e||0===t||isNaN(e)||isNaN(t)?0:(e/t*100).toFixed(2)},extend:function(){var e=arguments;if(e.length){if(1===e.length)return e[0];var t=Array.prototype.shift.call(e);r.is.object(t)||(t={});for(var n=e.length,s=0;s<n;s++){var a=e[s];r.is.object(a)||(a={});for(var l in a)a[l]&&a[l].constructor&&a[l].constructor===Object?(t[l]=t[l]||{},r.extend(t[l],a[l])):t[l]=a[l]}return t}},parseYouTubeId:function(e){var t=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;return e.match(t)?RegExp.$2:e},stripHTML:function(e){var n=t.createDocumentFragment(),s=t.createElement("div");return n.appendChild(s),s.innerHTML=e,n.firstChild.innerText},loadSprite:function(n,s){function a(e,n){e.innerHTML=n,t.body.insertBefore(e,t.body.childNodes[0])}if("string"==typeof n){var l="cache-",r="string"==typeof s,i=!1;if(!r||!t.querySelectorAll("#"+s).length){var u=t.createElement("div");if(u.setAttribute("hidden",""),r&&u.setAttribute("id",s),o.storage){var c=e.localStorage.getItem(l+s);if(i=null!==c){var d=JSON.parse(c);a(u,d.content)}}var p=new XMLHttpRequest;if(!("withCredentials"in p))return;p.open("GET",n,!0),p.onload=function(){o.storage&&e.localStorage.setItem(l+s,JSON.stringify({content:p.responseText})),a(u,p.responseText)},p.send()}}}},i=function(){var e=function(){var e=!1;return r.is.function(t.cancelFullScreen)?e="":["webkit","o","moz","ms","khtml"].some(function(n){return r.is.function(t[n+"CancelFullScreen"])?(e=n,!0):r.is.function(t.msExitFullscreen)&&t.msFullscreenEnabled?(e="ms",!0):void 0}),e}();return{prefix:e,eventType:"ms"===e?"MSFullscreenChange":e+"fullscreenchange",isFullScreen:function(n){if(!o.fullscreen)return!1;switch(r.is.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!!o.fullscreen&&(r.is.htmlElement(n)||(n=t.body),""===e?n.requestFullScreen():n[e+("ms"===e?"RequestFullscreen":"RequestFullScreen")]())},cancelFullScreen:function(){return!!o.fullscreen&&(""===e?t.cancelFullScreen():t[e+("ms"===e?"ExitFullscreen":"CancelFullScreen")]())},element:function(){return o.fullscreen?""===e?t.fullscreenElement:t[e+"FullscreenElement"]:null}}}(),o={audio:"canPlayType"in t.createElement("audio"),video:"canPlayType"in t.createElement("video"),fullscreen:i.prefix!==!1,storage:function(){if(!("localStorage"in e))return!1;var t="___test";try{return e.localStorage.setItem(t,t),e.localStorage.removeItem(t),!0}catch(e){return!1}return!1}(),pip:function(){var e=r.getBrowser();return!e.isIPhone&&r.is.function(r.createElement("video").webkitSetPresentationMode)}(),airplay:r.is.function(e.WebKitPlaybackTargetAvailabilityEvent),inline:"playsInline"in t.createElement("video"),mime:function(e,t){var n=e.media;try{if(!r.is.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},textTracks:"textTracks"in t.createElement("video"),passiveListeners:function(){var t=!1;try{var n=Object.defineProperty({},"passive",{get:function(){t=!0}});e.addEventListener("test",null,n)}catch(e){}return t}(),touch:"ontouchstart"in t.documentElement};return n});
\ 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..3a3d9ee9 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); } @@ -126,7 +126,7 @@ var build = { .pipe(less()) .on("error", gutil.log) .pipe(concat(key)) - .pipe(prefix(["last 2 versions"], { cascade: true })) + .pipe(prefix(["last 2 versions"], { cascade: false })) .pipe(cleanCSS()) .pipe(gulp.dest(paths[bundle].output)); }); @@ -145,7 +145,7 @@ var build = { .pipe(sass()) .on("error", gutil.log) .pipe(concat(key)) - .pipe(prefix(["last 2 versions"], { cascade: true })) + .pipe(prefix(["last 2 versions"], { cascade: false })) .pipe(cleanCSS()) .pipe(gulp.dest(paths[bundle].output)); }); @@ -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 */ @@ -30,7 +30,7 @@ - No PiP or AirPlay for Vimeo/YouTube - Settings won't be supported for custom controls (coming soon, need to work on templating) - Added `playsinline` support for iOS 10 -- Embed setup now accept an <iframe> as the target element for true progressive enhancement +- Embed setup now accepts an <iframe> as the target element for true progressive enhancement #### Breaking changes - New config options for loop diff --git a/package.json b/package.json index fc2c9930..d53cc9ee 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": [ @@ -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>—</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>—</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>—</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>—</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>—</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> @@ -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 + } }); ``` @@ -774,14 +784,9 @@ These events also bubble up the DOM. The event target will be the container elem </thead> <tbody> <tr> - <td><code>setup</code></td> - <td></td> - <td>When an initial setup has completed</td> - </tr> - <tr> <td><code>ready</code></td> <td></td> - <td>Triggered when the instance is ready for API use and external APIs are ready (in the case of YouTube and Vimeo).</td> + <td>Triggered when the instance is ready for API calls.</td> </tr> <tr> <td><code>canplay</code></td> @@ -979,6 +984,21 @@ 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> @@ -1032,9 +1052,6 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi Any unsupported browsers will display links to download the media if the correct html is used. -### Checking for support -There's an API method for checking support. You can call `plyr.supported()` and optionally pass a type to it, e.g. `plyr.supported("video")`. It will return an object with two keys; `basic` meaning there's basic support for that media type (or both if no type is passed) and `full` meaning there's full support for plyr. - ## Issues If you find anything weird with Plyr, please let us know using the GitHub issues tracker. diff --git a/src/js/plyr.js b/src/js/plyr.js index 352e0114..c41d564b 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,656 +1,1022 @@ // ========================================================================== // Plyr -// plyr.js v2.0.13 -// https://github.com/sampotts/plyr +// plyr.js v3.0.0 +// 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); + root.Plyr = factory(root, document); } }(typeof window !== 'undefined' ? window : this, function(window, document) { 'use strict'; + /* global jQuery */ // 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.13/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, + title: '', + debug: false, + autoplay: false, + seekTime: 10, + volume: 10, + 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 settings + quality: { + default: 'auto', + selected: 'auto' }, + + // Set loops + loop: { + active: false, + start: 0, + end: null, + indicator: { + start: 0, + end: 0 + } + }, + + // Speed up/down + speed: { + selected: 1.0, + options: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0] + }, + + // Keyboard shortcut settings + keyboardShortcuts: { + focused: true, + global: false + }, + + // Display tooltips tooltips: { - controls: false, - seek: true + controls: false, + seek: true }, + + // Selectors + // Change these to match your template if using custom HTML selectors: { - html5: 'video, audio', - embed: '[data-type]', - editable: 'input, textarea, select, [contenteditable]', - container: '.plyr', + 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"]', + loop: '[data-plyr="loop"]' }, - volume: { - input: '[data-plyr="volume"]', - display: '.plyr__volume--display' + inputs: { + seek: '[data-plyr="seek"]', + volume: '[data-plyr="volume"]', + speed: '[data-plyr="speed"]', + language: '[data-plyr="language"]', + quality: '[data-plyr="quality"]' }, - 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' + } }, + + // Class hooks added to the player in different states 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 settings captions: { - defaultActive: false + active: false, + language: window.navigator.language.split("-")[0] }, + + // Fullscreen settings fullscreen: { - enabled: true, - fallback: true, - allowAudio: false + enabled: true, + fallback: true, + allowAudio: false }, + + // Local storage storage: { - enabled: true, - key: 'plyr' + enabled: true, + key: 'plyr' }, - controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'], + + // Default controls + controls: [ + 'play-large', + 'play', + 'progress', + 'current-time', + 'mute', + 'volume', + 'captions', + 'settings', + 'pip', + 'airplay', + 'fullscreen' + ], + + // Localisation 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'] + 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', + none: 'None' }, + // 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 to watch on HTML5 media elements and bubble + // https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events + events: [ + 'ended', + 'progress', + 'stalled', + 'playing', + 'waiting', + 'canplay', + 'canplaythrough', + 'loadstart', + 'loadeddata', + 'loadedmetadata', + 'timeupdate', + 'volumechange', + 'play', + 'pause', + 'error', + 'seeking', + 'seeked', + 'emptied' + ], + // Logging - logPrefix: '[Plyr]' + logPrefix: '' + }; + + // Types + var types = { + embed: ['youtube', 'vimeo', 'soundcloud'], + html5: ['video', 'audio'] }; - // 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; - - if ((navigator.appVersion.indexOf('Windows NT') !== -1) && (navigator.appVersion.indexOf('rv:11') !== -1)) { - // MSIE 11 - isIE = true; - name = 'IE'; - fullVersion = '11'; - } else if ((verOffset = ua.indexOf('MSIE')) !== -1) { - // MSIE - isIE = true; - name = 'IE'; - fullVersion = ua.substring(verOffset + 5); - } else if ((verOffset = ua.indexOf('Chrome')) !== -1) { - // Chrome - isChrome = true; - name = 'Chrome'; - fullVersion = ua.substring(verOffset + 7); - } else if ((verOffset = ua.indexOf('Safari')) !== -1) { - // Safari - isSafari = true; - name = 'Safari'; - fullVersion = ua.substring(verOffset + 7); - if ((verOffset = ua.indexOf('Version')) !== -1) { + // Utilities outside of Plyr scope + var utils = { + // Check variable types + is: { + object: function(input) { + return input !== null && typeof(input) === 'object' && input.constructor === Object; + }, + array: function(input) { + return input !== null && Array.isArray(input); + }, + 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; + }, + cue: function(input) { + return input !== null && (input instanceof window.TextTrackCue || input instanceof window.VTTCue); + }, + track: function(input) { + return input !== null && input instanceof window.TextTrack; + }, + 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 + getBrowser: function() { + 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 + isIE = true; + name = 'IE'; + fullVersion = '11'; + } else if ((verOffset = ua.indexOf('MSIE')) !== -1) { + // MSIE + isIE = true; + name = 'IE'; + fullVersion = ua.substring(verOffset + 5); + } else if ((verOffset = ua.indexOf('Chrome')) !== -1) { + // Chrome + isChrome = true; + name = 'Chrome'; + fullVersion = ua.substring(verOffset + 7); + } else if ((verOffset = ua.indexOf('Safari')) !== -1) { + // Safari + isSafari = true; + name = 'Safari'; + fullVersion = ua.substring(verOffset + 7); + + if ((verOffset = ua.indexOf('Version')) !== -1) { + fullVersion = ua.substring(verOffset + 8); + } + } else if ((verOffset = ua.indexOf('Firefox')) !== -1) { + // Firefox + isFirefox = true; + name = 'Firefox'; 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); + fullVersion = ua.substring(verOffset + 1); + + if (name.toLowerCase() === name.toUpperCase()) { + name = navigator.appName; + } } - } else if ((verOffset = ua.indexOf('Firefox')) !== -1) { - // Firefox - isFirefox = true; - name = 'Firefox'; - 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); - fullVersion = ua.substring(verOffset + 1); - if (name.toLowerCase() === name.toUpperCase()) { - name = navigator.appName; + // Trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); } - } - // Trim the fullVersion string at semicolon/space if present - if ((ix = fullVersion.indexOf(';')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } - if ((ix = fullVersion.indexOf(' ')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } + // Get major version + majorVersion = parseInt('' + fullVersion, 10); + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } - // Get major version - majorVersion = parseInt('' + fullVersion, 10); - if (isNaN(majorVersion)) { - fullVersion = '' + parseFloat(navigator.appVersion); - majorVersion = parseInt(navigator.appVersion, 10); - } + // Return data + return { + name: name, + version: majorVersion, + isIE: isIE, + isOldIE: (isIE && majorVersion <= 9), + isFirefox: isFirefox, + isChrome: isChrome, + isSafari: isSafari, + isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), + isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform) + }; + }, - // 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 - }; - } + // Check for support + // Basic functionality vs full UI + checkSupport: function(type, inline) { + var basic = false; + var full = false; + var browser = utils.getBrowser(); + var playsInline = (browser.isIPhone && inline && support.inline); + + switch (type) { + case 'video': + basic = support.video; + full = basic && !browser.isOldIE && (!browser.isIPhone || playsInline); + break; + + case 'audio': + basic = support.audio; + full = basic && !browser.isOldIE; + break; + + case 'youtube': + basic = support.video; + full = basic && !browser.isOldIE && (!browser.isIPhone || playsInline); + break; - // 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; + case 'vimeo': + case 'soundcloud': + basic = true; + full = (!browser.isOldIE && !browser.isIos); + break; - 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/, '')); + default: + basic = (support.audio && support.video); + full = (basic && !browser.isOldIE); } - } 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/, '')); + + return { + basic: basic, + full: full + }; + }, + + // Inject a script + injectScript: function(url) { + // Check script is not already referenced + if (document.querySelectorAll('script[src="' + url + '"]').length) { + return; } - } - // If we got this far, we're stuffed - return false; - } + var tag = document.createElement('script'); + tag.src = url; - // Inject a script - function _injectScript(source) { - if (document.querySelectorAll('script[src="' + source + '"]').length) { - return; - } + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + }, - 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 + inFrame: function() { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + }, - // Element exists in an array - function _inArray(haystack, needle) { - return Array.prototype.indexOf && (haystack.indexOf(needle) !== -1); - } + // Element exists in an array + inArray: function(haystack, needle) { + return utils.is.array(haystack) && haystack.indexOf(needle) !== -1; + }, - // Replace all - function _replaceAll(string, find, replace) { - return string.replace(new RegExp(find.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), replace); - } + // Replace all + replaceAll: function(string, find, replace) { + return string.replace(new RegExp(find.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), replace); + }, - // Wrap an element - function _wrap(elements, wrapper) { - // Convert `elements` to an array, if necessary. - if (!elements.length) { - elements = [elements]; - } + // Wrap an element + wrap: function(elements, wrapper) { + // Convert `elements` to an array, if necessary. + if (!elements.length) { + elements = [elements]; + } - // 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 element = elements[i]; + // 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 element = elements[i]; - // Cache the current parent and sibling. - var parent = element.parentNode; - var sibling = element.nextSibling; + // Cache the current parent and sibling. + var parent = element.parentNode; + var sibling = element.nextSibling; - // Wrap the element (is automatically removed from its current - // parent). - child.appendChild(element); + // Wrap the element (is automatically removed from its current + // parent). + child.appendChild(element); - // If the element had a sibling, insert the wrapper before - // the sibling to maintain the HTML structure; otherwise, just - // append it to the parent. - if (sibling) { - parent.insertBefore(child, sibling); - } else { - parent.appendChild(child); + // If the element had a sibling, insert the wrapper before + // the sibling to maintain the HTML structure; otherwise, just + // append it to the parent. + if (sibling) { + parent.insertBefore(child, sibling); + } else { + parent.appendChild(child); + } + + return child; } + }, - return child; - } - } + // Remove an element + removeElement: function(element) { + if (!utils.is.htmlElement(element) || + !utils.is.htmlElement(element.parentNode)) { + return; + } - // 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; + element.parentNode.removeChild(element); + }, - // Move all children out of the element - while (wrapper.firstChild) { - parent.insertBefore(wrapper.firstChild, wrapper); - } + // Prepend child + prependChild: function(parent, element) { + parent.insertBefore(element, parent.firstChild); + }, - // Remove the empty element - parent.removeChild(wrapper); - }*/ + // Inaert an element after another + insertAfter: function(element, target) { + target.parentNode.insertBefore(element, target.nextSibling); + }, - // Remove an element - function _remove(element) { - if (!element) { - return; - } - element.parentNode.removeChild(element); - } + // Create a DocumentFragment + createElement: function(type, attributes, text) { + // Create a new <element> + var element = document.createElement(type); - // Prepend child - function _prependChild(parent, element) { - parent.insertBefore(element, parent.firstChild); - } + // Set all passed attributes + if (utils.is.object(attributes)) { + utils.setAttributes(element, attributes); + } - // Set attributes - function _setAttributes(element, attributes) { - for (var key in attributes) { - element.setAttribute(key, (_is.boolean(attributes[key]) && attributes[key]) ? '' : attributes[key]); - } - } + // Add text node + if (utils.is.string(text)) { + element.textContent = text; + } - // Insert a HTML element - function _insertElement(type, parent, attributes) { - // Create a new <element> - var element = document.createElement(type); + // Return built element + return element; + }, - // Set all passed attributes - _setAttributes(element, attributes); + // Insert a DocumentFragment + insertElement: function(type, parent, attributes, text) { + // Create a new <element> + var element = utils.createElement(type, attributes, text); - // Inject the new element - _prependChild(parent, element); - } + // Inject the new element + utils.prependChild(parent, element); + }, - // Get a classname from selector - function _getClassname(selector) { - return selector.replace('.', ''); - } + // Remove all child elements + emptyElement: function(element) { + var length = element.childNodes.length; + while (length--) { + element.removeChild(element.lastChild); + } + }, - // Toggle class on an element - function _toggleClass(element, className, state) { - if (element) { - if (element.classList) { - element.classList[state ? 'add' : 'remove'](className); - } else { - var name = (' ' + element.className + ' ').replace(/\s+/g, ' ').replace(' ' + className + ' ', ''); - element.className = name + (state ? ' ' + className : ''); + // Set attributes + setAttributes: function(element, attributes) { + for (var key in attributes) { + element.setAttribute(key, attributes[key]); } - } - } + }, - // Has class name - function _hasClass(element, className) { - if (element) { - if (element.classList) { - return element.classList.contains(className); - } else { - return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className); + // Get an attribute object from a string selector + getAttributesFromSelector: function(selector, existingAttributes) { + // For example: + // '.test' to { class: 'test' } + // '#test' to { id: 'test' } + // '[data-test="test"]' to { 'data-test': 'test' } + + if (!utils.is.string(selector) || utils.is.empty(selector)) { + return {}; } - } - return false; - } - // Element matches selector - function _matches(element, selector) { - var p = Element.prototype; + var attributes = {}; - var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) { - return [].indexOf.call(document.querySelectorAll(s), this) !== -1; - }; + selector.split(',').forEach(function(selector) { + // Remove whitespace + selector = selector.trim(); - return f.call(element, selector); - } + // Get the first character + var start = selector.charAt(0); - // Bind along with custom handler - function _proxyListener(element, eventName, userListener, defaultListener, useCapture) { - _on(element, eventName, function(event) { - if (userListener) { - userListener.apply(element, [event]); - } - defaultListener.apply(element, [event]); - }, useCapture); - } + switch (start) { + case '.': + // Classname selector + var className = selector.replace('.', ''); - // Toggle event listener - function _toggleListener(element, events, callback, toggle, useCapture) { - var eventList = events.split(' '); + // Add to existing classname + if (utils.is.object(existingAttributes) && utils.is.string(existingAttributes.class)) { + existingAttributes.class += ' ' + className; + } - // Whether the listener is a capturing listener or not - // Default to false - if (!_is.boolean(useCapture)) { - useCapture = false; - } + 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; - // 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]); + break; } - } - return; - } + }); - // 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); - } - } + return attributes; + }, - // Bind event - function _on(element, events, callback, useCapture) { - if (element) { - _toggleListener(element, events, callback, true, useCapture); - } - } + // Toggle class on an element + toggleClass: function(element, className, state) { + if (element) { + if (element.classList) { + element.classList[state ? 'add' : 'remove'](className); + } else { + var name = (' ' + element.className + ' ').replace(/\s+/g, ' ').replace(' ' + className + ' ', ''); + element.className = name + (state ? ' ' + className : ''); + } + } + }, - // Unbind event - /*function _off(element, events, callback, useCapture) { - if (element) { - _toggleListener(element, events, callback, false, useCapture); - } - }*/ + // Has class name + hasClass: function(element, className) { + if (element) { + if (element.classList) { + return element.classList.contains(className); + } else { + return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className); + } + } + return false; + }, - // Trigger event - function _event(element, type, bubbles, properties) { - // Bail if no element - if (!element || !type) { - return; - } + // Element matches selector + matches: function(element, selector) { + var prototype = Element.prototype; - // Default bubbles to false - if (!_is.boolean(bubbles)) { - bubbles = false; - } + var matches = prototype.matches || + prototype.webkitMatchesSelector || + prototype.mozMatchesSelector || + prototype.msMatchesSelector || + function(selector) { + return [].indexOf.call(document.querySelectorAll(selector), this) !== -1; + }; - // Create and dispatch the event - var event = new CustomEvent(type, { - bubbles: bubbles, - detail: properties - }); + return matches.call(element, selector); + }, - // Dispatch the event - element.dispatchEvent(event); - } + // Get the focused element + getFocusElement: function() { + var focused = document.activeElement; - // 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) { - // Bail if no target - if (!target) { - return; - } + if (!focused || focused === document.body) { + focused = null; + } else { + focused = document.querySelector(':focus'); + } - // Get state - state = (_is.boolean(state) ? state : !target.getAttribute('aria-pressed')); + return focused; + }, - // Set the attribute on target - target.setAttribute('aria-pressed', state); + // Bind along with custom handler + proxy: function(element, eventName, customListener, defaultListener, passive, capture) { + utils.on(element, eventName, function(event) { + if (customListener) { + customListener.apply(element, [event]); + } + defaultListener.apply(element, [event]); + }, passive, capture); + }, - return state; - } + // Toggle event listener + toggleListener: function(elements, events, callback, toggle, passive, capture) { + events = events.split(' '); - // Get percentage - function _getPercentage(current, max) { - if (current === 0 || max === 0 || isNaN(current) || isNaN(max)) { - return 0; - } - return ((current / max) * 100).toFixed(2); - } + // Whether the listener is a capturing listener or not + // Default to false + if (!utils.is.boolean(capture)) { + capture = false; + } - // 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() { - // Get arguments - var objects = arguments; + // Whether the listener can be passive (i.e. default never prevented) + // Default to true + if (!utils.is.boolean(passive)) { + passive = true; + } - // Bail if nothing to merge - if (!objects.length) { - return; - } + // If a nodelist is passed, call itself on each node + if (elements instanceof NodeList) { + // Convert arguments to Array + // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments + var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments); - // Return first if specified but nothing to merge - if (objects.length === 1) { - return objects[0]; - } + // Remove the first argument (elements) as we replace it + args.shift(); - // First object is the destination - var destination = Array.prototype.shift.call(objects), - length = objects.length; + // Create listener for each node + [].forEach.call(elements, function(element) { + if (element instanceof Node) { + utils.toggleListener.apply(null, [element].concat(args)); + } + }); - // Loop through all objects to merge - for (var i = 0; i < length; i++) { - var source = objects[i]; + return; + } - for (var property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - _extend(destination[property], source[property]); - } else { - destination[property] = source[property]; - } + // Build options + // Default to just capture boolean + var options = capture; + + // If passive events listeners are supported + if (support.passiveListeners) { + options = { + passive: passive, + capture: capture + }; } - } - return destination; - } + // If a single node is passed, bind the event listener + events.forEach(function(event) { + elements[toggle ? 'addEventListener' : 'removeEventListener'](event, callback, options); + }); + }, - // Check variable types - var _is = { - object: function(input) { - return input !== null && typeof(input) === 'object'; + // Bind event handler + on: function(element, events, callback, passive, capture) { + if (!utils.is.undefined(element)) { + utils.toggleListener(element, events, callback, true, passive, capture); + } }, - array: function(input) { - return input !== null && (typeof(input) === 'object' && input.constructor === Array); + + // Unbind event handler + off: function(element, events, callback, passive, capture) { + if (!utils.is.undefined(element)) { + utils.toggleListener(element, events, callback, false, passive, capture); + } }, - number: function(input) { - return input !== null && (typeof(input) === 'number' && !isNaN(input - 0) || (typeof input === 'object' && input.constructor === Number)); + + // Trigger event + event: function(element, type, bubbles, properties) { + // Bail if no element + if (!element || !type) { + return; + } + + // Default bubbles to false + if (!utils.is.boolean(bubbles)) { + bubbles = false; + } + + // Create CustomEvent constructor + var CustomEvent; + if (utils.is.function(window.CustomEvent)) { + CustomEvent = window.CustomEvent; + } else { + // Polyfill CustomEvent + // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill + CustomEvent = function(event, params) { + params = params || { + bubbles: false, + cancelable: false, + detail: undefined + }; + var custom = document.createEvent('CustomEvent'); + custom.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return custom; + }; + CustomEvent.prototype = window.Event.prototype; + } + + // Create and dispatch the event + var event = new CustomEvent(type, { + bubbles: bubbles, + detail: properties + }); + + // Dispatch the event + element.dispatchEvent(event); }, - string: function(input) { - return input !== null && (typeof input === 'string' || (typeof input === 'object' && input.constructor === String)); + + // Toggle aria-pressed state on a toggle button + // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles + toggleState: function(target, state) { + // Bail if no target + if (!target) { + return; + } + + // Get state + state = (utils.is.boolean(state) ? state : !target.getAttribute('aria-pressed')); + + // Set the attribute on target + target.setAttribute('aria-pressed', state); + + return state; }, - boolean: function(input) { - return input !== null && typeof input === 'boolean'; + + // Get percentage + getPercentage: function(current, max) { + if (current === 0 || max === 0 || isNaN(current) || isNaN(max)) { + return 0; + } + return ((current / max) * 100).toFixed(2); }, - nodeList: function(input) { - return input !== null && input instanceof NodeList; + + // 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) + extend: function() { + // Get arguments + var objects = arguments; + + // Bail if nothing to merge + if (!objects.length) { + return; + } + + // Return first if specified but nothing to merge + if (objects.length === 1) { + return objects[0]; + } + + // First object is the destination + var destination = Array.prototype.shift.call(objects); + if (!utils.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 (!utils.is.object(source)) { + source = {}; + } + + for (var property in source) { + if (source[property] && source[property].constructor && source[property].constructor === Object) { + destination[property] = destination[property] || {}; + utils.extend(destination[property], source[property]); + } else { + destination[property] = source[property]; + } + } + } + + return destination; }, - htmlElement: function(input) { - return input !== null && input instanceof HTMLElement; + + // Parse YouTube ID from url + parseYouTubeId: function(url) { + var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; + return url.match(regex) ? RegExp.$2 : url; }, - function: function(input) { - return input !== null && typeof input === 'function'; + + // Remove HTML from a string + stripHTML: function(source) { + var fragment = document.createDocumentFragment(); + var element = document.createElement('div'); + fragment.appendChild(element); + element.innerHTML = source; + return fragment.firstChild.innerText; }, - undefined: function(input) { - return input !== null && typeof input === 'undefined'; - } - }; - // Parse YouTube ID from url - function _parseYouTubeId(url) { - var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; - return (url.match(regex)) ? RegExp.$2 : url; - } + // Load an SVG sprite + loadSprite: function(url, id) { + if (typeof url !== 'string') { + return; + } - // Parse Vimeo ID from url - function _parseVimeoId(url) { - var regex = /^.*(vimeo.com\/|video\/)(\d+).*/; - return (url.match(regex)) ? RegExp.$2 : url; - } + var prefix = 'cache-'; + var hasId = typeof id === 'string'; + var isCached = false; - // 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(' '); - - // 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; + function updateSprite(container, data) { + // Inject content + container.innerHTML = data; + + // Inject the SVG to the body + document.body.insertBefore(container, document.body.childNodes[0]); + } + + // Only load once + if (!hasId || !document.querySelectorAll('#' + id).length) { + // Create container + var container = document.createElement('div'); + container.setAttribute('hidden', ''); + + if (hasId) { + container.setAttribute('id', id); + } + + // Check in cache + if (support.storage) { + var cached = window.localStorage.getItem(prefix + id); + isCached = cached !== null; + + if (isCached) { + var data = JSON.parse(cached); + updateSprite(container, data.content); + } + } + + // ReSharper disable once InconsistentNaming + var xhr = new XMLHttpRequest(); + + // XHR for Chrome/Firefox/Opera/Safari + if ('withCredentials' in xhr) { + xhr.open('GET', url, true); + } else { + return; } + + // Once loaded, inject to container and body + xhr.onload = function() { + if (support.storage) { + window.localStorage.setItem(prefix + id, JSON.stringify({ + content: xhr.responseText + })); + } + + updateSprite(container, xhr.responseText); + }; + + xhr.send(); } } + }; - // Update methods to do something useful - if (fullscreen.supportsFullScreen) { + // Fullscreen API + var fullscreen = (function() { + // Determine the prefix + var prefix = (function() { + var value = false; + + if (utils.is.function(document.cancelFullScreen)) { + value = ''; + } else { + // Check for fullscreen support by vendor prefix + ['webkit', 'o', 'moz', 'ms', 'khtml'].some(function(prefix) { + if (utils.is.function(document[prefix + 'CancelFullScreen'])) { + value = prefix; + return true; + } else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) { + // Special case for MS (when isn't it?) + value = 'ms'; + return true; + } + }); + } + + 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 (utils.is.undefined(element)) { element = document.body; } switch (this.prefix) { @@ -659,783 +1025,1374 @@ 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 (!utils.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 (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']; + } + }; + })(); - return fullscreen; - } + // Check for feature support + var support = { + // Basic support + audio: 'canPlayType' in document.createElement('audio'), + video: 'canPlayType' in document.createElement('video'), + + // Fullscreen support and set prefix + fullscreen: fullscreen.prefix !== false, - // Local storage - var _storage = { - supported: (function() { + // Local storage + // We can't assume if local storage is present that we can use it + storage: (function() { if (!('localStorage' in window)) { return false; } // Try to use it (it might be disabled, e.g. user is in private/porn mode) - // see: https://github.com/sampotts/plyr/issues/131 + // see: https://github.com/Selz/plyr/issues/131 + var test = '___test'; try { - // Add test item - window.localStorage.setItem('___test', 'OK'); + window.localStorage.setItem(test, test); + window.localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } + + return false; + })(), - // Get the test item - var result = window.localStorage.getItem('___test'); + // Picture-in-picture support + // Safari only currently + pip: (function() { + var browser = utils.getBrowser(); + return !browser.isIPhone && utils.is.function(utils.createElement('video').webkitSetPresentationMode); + })(), - // Clean up - window.localStorage.removeItem('___test'); + // Airplay support + // Safari only currently + airplay: utils.is.function(window.WebKitPlaybackTargetAvailabilityEvent), - // Check if value matches - return (result === 'OK'); - } - catch (e) { + // Inline playback support + // https://webkit.org/blog/6784/new-video-policies-for-ios/ + inline: 'playsInline' in document.createElement('video'), + + // 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 (!utils.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; - })() + }, + + // Check for textTracks support + textTracks: 'textTracks' in document.createElement('video'), + + // Check for passive event listener support + // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md + // https://www.youtube.com/watch?v=NPM6172J22g + passiveListeners: (function() { + // Test via a getter in the options object to see if the passive property is accessed + var supported = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + supported = true; + } + }); + window.addEventListener("test", null, options); + } catch (e) {} + + return supported; + })(), + + // Touch + // Remember a device can be moust + touch enabled + touch: 'ontouchstart' in document.documentElement }; // Player instance - function Plyr(media, config) { - var plyr = this, - timers = {}, - api; + function Player(element, options) { + var player = this; + var timers = {}; + var api = {}; + + // String selector passed + if (utils.is.string(element)) { + element = document.querySelectorAll(element); + } - // Set media - plyr.media = media; - var original = media.cloneNode(true); + // jQuery, NodeList or Array passed, use first element + if ((window.jQuery && element instanceof jQuery) || utils.is.nodeList(element) || utils.is.array(element)) { + element = element[0]; + } + + // Config + var config = utils.extend({}, defaults, options, (function() { + try { + return JSON.parse(element.getAttribute('data-plyr')); + } catch (e) {} + })()); + + // Elements cache + player.elements = { + container: null, + buttons: {}, + display: {}, + progress: {}, + inputs: {}, + settings: { + menu: null, + panes: {}, + tabs: {} + }, + media: element, + captions: null + }; + + // Captions + player.captions = { + enabled: false, + captions: [], + tracks: [], + currentTrack: null + }; + + // Fullscreen + player.fullscreen = { + active: false + }; + + // Debugging + var log = function() {}; + var warn = function() {}; + var error = function() {}; + if (config.debug && 'console' in window) { + log = window.console.log; + warn = window.console.warn; + error = window.console.error; + } + + // Log config options and support + log('Config', config); + log('Support', support); // Trigger events, with plyr instance passed - function _triggerEvent(element, type, bubbles, properties) { - _event(element, type, bubbles, _extend({}, properties, { + function trigger(element, type, bubbles, properties) { + utils.event(element, type, bubbles, utils.extend({}, properties, { plyr: api })); } - // Debugging - function _console(type, args) { - if (config.debug && window.console) { - args = Array.prototype.slice.call(args); + // Find all elements + function getElements(selector) { + return player.elements.container.querySelectorAll(selector); + } - if (_is.string(config.logPrefix) && config.logPrefix.length) { - args.unshift(config.logPrefix); - } + // Find a single element + function getElement(selector) { + return getElements(selector)[0]; + } + + function removeElement(element) { + // Remove reference from player.elements cache + if (utils.is.string(element)) { + utils.removeElement(player.elements[element]); + player.elements[element] = null; - console[type].apply(console, args); + } else { + utils.removeElement(element); } } - var _log = function() { _console('log', arguments) }, - _warn = function() { _console('warn', arguments) }; - // Log config options - _log('Config', config); + // 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 + utils.on(player.elements.container, 'keydown', checkFocus, false); + } + + // Add elements to HTML5 media (source, tracks, etc) + function insertElements(type, attributes) { + if (utils.is.string(attributes)) { + utils.insertElement(type, player.elements.media, { + src: attributes + }); + } else if (utils.is.array(attributes)) { + attributes.forEach(function(attribute) { + utils.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'); + utils.setAttributes(icon, utils.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 utils.createElement('span', { + class: config.classes.hidden + }, text); + } + + // Create a badge + function createBadge(text) { + var badge = utils.createElement('span', { + class: config.classes.menu.value + }); + + badge.appendChild(utils.createElement('span', { + class: config.classes.menu.badge + }, text)); + + return badge; + } + + // Create a <button> + function createButton(type, attributes) { + var button = utils.createElement('button'); + var iconDefault; + var iconToggled; + var labelKey; + + if (!utils.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 + utils.extend(attributes, utils.getAttributesFromSelector(config.selectors.buttons[type], attributes)); + + // Add toggle icon if needed + if (utils.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 + utils.setAttributes(button, attributes); + + player.elements.buttons[type] = button; + + return button; + } + + // Create an <input type='range'> + function createRange(type, attributes) { + // Seek label + var label = utils.createElement('label', { + for: attributes.id, + class: config.classes.hidden + }, config.i18n[type]); + + // Seek input + var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(config.selectors.inputs[type]), { + type: 'range', + min: 0, + max: 100, + step: 0.1, + value: 0, + autocomplete: 'off' + }, attributes)); + + player.elements.inputs[type] = input; + + return { + label: label, + input: input + } + } + + // Create a <progress> + function createProgress(type, attributes) { + var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(config.selectors.display[type]), { + min: 0, + max: 100, + value: 0 + }, attributes)); + + // Create the label inside + if (type !== 'volume') { + progress.appendChild(utils.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 = utils.createElement('span', { + class: 'plyr__time' + }); + + container.appendChild(utils.createElement('span', { + class: config.classes.hidden + }, config.i18n[type])); + + container.appendChild(utils.createElement('span', utils.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 = utils.createElement('div', utils.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 (utils.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 (utils.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 (utils.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 (utils.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 (utils.inArray(config.controls, 'progress')) { + var container = utils.createElement('span', utils.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 = utils.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 (utils.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 (utils.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 (utils.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 (utils.inArray(config.controls, 'volume')) { + var volume = utils.createElement('span', { + class: 'plyr__volume' + }); + + // Set the attributes + var attributes = { + max: 10, + value: config.volume + }; + + // Create the volume range slider + var range = createRange('volume', utils.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 (utils.inArray(config.controls, 'captions')) { + controls.appendChild(createButton('captions')); + } + + // Settings button / menu + if (utils.inArray(config.controls, 'settings')) { + var menu = utils.createElement('span', utils.extend(utils.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 = utils.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 = utils.createElement('div'); + + var home = utils.createElement('div', { + id: 'plyr-settings-' + data.id + '-home', + 'aria-hidden': false, + 'aria-labelled-by': 'plyr-settings-toggle-' + data.id, + role: 'tabpanel' + }); + + var tabs = utils.createElement('ul', { + role: 'tablist' + }); + + ['captions', 'quality', 'speed', 'loop'].forEach(function(type) { + var tab = utils.createElement('li', { + role: 'tab' + }); + + var button = utils.createElement('button', utils.extend(utils.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 = utils.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 = utils.createElement('div', { + id: 'plyr-settings-' + data.id + '-' + type, + 'aria-hidden': true, + 'aria-labelled-by': 'plyr-settings-' + data.id + '-' + type + '-tab', + role: 'tabpanel', + tabindex: -1 + }); + + var back = utils.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 = utils.createElement('ul'); + + 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; + } + + // Picture in picture button + if (utils.inArray(config.controls, 'pip') && support.pip) { + controls.appendChild(createButton('pip')); + } + + // Airplay button + if (utils.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 (utils.inArray(config.controls, 'fullscreen')) { + controls.appendChild(createButton('fullscreen')); + } + + player.elements.controls = controls; + + setLoopMenu(); + setSpeedMenu(); + + return controls; + } + + // Set the YouTube quality menu + // TODO: Support for HTML5 + // YouTube: "hd2160", "hd1440", "hd1080", "hd720", "large", "medium", "small", "tiny", "auto" + function setQualityMenu(options, current) { + var list = player.elements.settings.panes.quality.querySelector('ul'); + + // Empty the menu + utils.emptyElement(list); + + // 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 (utils.is.array(options) && !utils.is.empty(options)) { + options.filter(function(quality) { + // Remove any unwanted quality levels + return !utils.inArray(['tiny', 'small'], quality); + }).forEach(function(quality) { + var item = utils.createElement('li'); + + var label = utils.createElement('label', { + class: config.classes.control, + for: 'plyr-quality-' + quality + }); + + var radio = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(config.selectors.inputs.quality), { + type: 'radio', + id: 'plyr-quality-' + quality, + name: 'plyr-quality', + value: quality, + })); + + if (quality === config.quality.selected) { + radio.setAttribute('checked', ''); + } + + label.appendChild(radio); + label.appendChild(document.createTextNode(getLabel(quality))); + + var badge = getBadge(quality); + if (utils.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'); + + // Empty the menu + utils.emptyElement(list); + + options.forEach(function(option) { + var item = utils.createElement('li'); + + var button = utils.createElement('button', utils.extend(utils.getAttributesFromSelector(config.selectors.buttons.loop), { + type: 'button', + class: config.classes.control, + 'data-plyr-loop-action': option + }), config.i18n[option]); + + if (utils.inArray(['start', 'end'], option)) { + var badge = createBadge('0:00'); + button.appendChild(badge); + } + + item.appendChild(button); + + list.appendChild(item); + }); + } + + // Set a list of available captions languages + function setCaptionsMenu() { + var list = player.elements.settings.panes.captions.querySelector('ul'); + + // Empty the menu + utils.emptyElement(list); + + // If there's no captions, bail + if (utils.is.empty(player.captions.tracks)) { + return; + } + + // Re-map the tracks into just the data we need + var tracks = [].map.call(player.captions.tracks, function(track) { + return { + language: track.language, + badge: true, + label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase() + } + }); + + // Add the "None" option to turn off captions + tracks.unshift({ + language: 'off', + label: config.i18n.none + }); + + // Generate options + tracks.forEach(function(track) { + var item = utils.createElement('li'); + + var label = utils.createElement('label', { + class: config.classes.control, + for: 'plyr-language-' + track.language + }); + + var radio = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(config.selectors.inputs.language), { + type: 'radio', + id: 'plyr-language-' + track.language, + name: 'plyr-language', + value: track.language, + })); + + if (track.language === config.captions.language.toLowerCase()) { + radio.setAttribute('checked', ''); + } + + label.appendChild(radio); + label.appendChild(document.createTextNode(track.label || track.language)); + + if (track.badge) { + label.appendChild(createBadge(track.language.toUpperCase())); + } + + item.appendChild(label); + + list.appendChild(item); + }); + } + + // Set a list of available captions languages + function setSpeedMenu(options) { + var list = player.elements.settings.panes.speed.querySelector('ul'); + + // Empty the menu + utils.emptyElement(list); + + // If there's no captions, bail + if (!utils.is.array(options)) { + options = config.speed.options; } - // Close everything - html.push('</div>'); + options.forEach(function(speed) { + var item = utils.createElement('li'); + + var label = utils.createElement('label', { + class: config.classes.control, + for: 'plyr-speed-' + speed.toString().replace('.', '-') + }); - return html.join(''); + var radio = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(config.selectors.inputs.speed), { + type: 'radio', + id: 'plyr-speed-' + speed.toString().replace('.', '-'), + name: 'plyr-speed', + value: speed, + })); + + if (speed === config.speed.selected) { + radio.setAttribute('checked', ''); + } + + label.appendChild(radio); + label.insertAdjacentHTML('beforeend', '×' + speed); + + item.appendChild(label); + + 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 && !utils.inFrame())) { + log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled'); // Add styling hook - _toggleClass(plyr.container, config.classes.fullscreen.enabled, true); + utils.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) { + utils.toggleState(player.elements.buttons.fullscreen, false); } // Setup focus trap - _focusTrap(); + focusTrap(); } } // Setup captions - function _setupCaptions() { - // Bail if not HTML5 video - if (plyr.type !== 'video') { + function setupCaptions(tracks) { + // Only Vimeo and HTML5 video supported at this point + if (!utils.inArray(['video', 'vimeo'], player.type) || (player.type === 'video' && !support.textTracks)) { return; } // Inject the container - if (!_getElement(config.selectors.captions)) { - plyr.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + _getClassname(config.selectors.captions) + '"></div>'); + if (!utils.is.htmlElement(player.elements.captions)) { + player.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(config.selectors.captions)); + utils.insertAfter(player.elements.captions, player.elements.wrapper); } - // Determine if HTML5 textTracks is supported - plyr.usingTextTracks = false; - if (plyr.media.textTracks) { - plyr.usingTextTracks = true; - } + // Get tracks + player.captions.tracks = utils.is.array(tracks) ? tracks : player.elements.media.textTracks; - // Get URL of caption file if exists - var captionSrc = '', - kind, - children = plyr.media.childNodes; + // Set the class hook + utils.toggleClass(player.elements.container, config.classes.captions.enabled, !utils.is.empty(player.captions.tracks)); - 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'); - } - } + // If no caption file exists, hide container for caption text + if (utils.is.empty(player.captions.tracks)) { + return; } - // Record if caption file exists or not - plyr.captionExists = true; - if (captionSrc === '') { - plyr.captionExists = false; - _log('No caption track found'); - } else { - _log('Caption track found; URI: ' + captionSrc); - } + // Enable UI + showCaptions(); - // If no caption file exists, hide container for caption text - if (!plyr.captionExists) { - _toggleClass(plyr.container, config.classes.captions.enabled); - } else { - // 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'; - } + if (player.type === 'video') { + var language = config.captions.language.toLowerCase(); - // Enable UI - _showCaptions(plyr); + // Turn off native caption rendering to avoid double captions + [].forEach.call(player.captions.tracks, function(track) { + // Remove previous bindings (if we've changed source or language) + utils.off(track, 'cuechange', setActiveCue); - // 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)) { + // Hide captions + track.mode = 'hidden'; - // Debugging - _log('Detected browser with known TextTrack issues - using manual fallback'); + // If language matches, it's the selected track + if (track.language === language) { + player.captions.currentTrack = track; + } + }); - // Set to false so skips to 'manual' captioning - plyr.usingTextTracks = false; + // If we couldn't get the requested language, we get the first + if (!utils.is.track(player.captions.currentTrack)) { + warn('No language found to match ' + language + ' in tracks'); + player.captions.currentTrack = player.captions.tracks[0]; } - // Rendering caption tracks - // Native support required - http://caniuse.com/webvtt - if (plyr.usingTextTracks) { - _log('TextTracks supported'); - - for (var y = 0; y < tracks.length; y++) { - var track = tracks[y]; + // If it's a caption or subtitle, render it + var track = player.captions.currentTrack; + if (utils.is.track(track) && utils.inArray(['captions', 'subtitles'], track.kind)) { + utils.on(track, 'cuechange', setActiveCue); - 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(); - } - }); - } - } - } else { - // Caption tracks not natively supported - _log('TextTracks not supported so rendering captions manually'); - - // Render captions from array at appropriate time - plyr.currentCaption = ''; - plyr.captions = []; - - if (captionSrc !== '') { - // Create XMLHttpRequest Object - var xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - var captions = [], - caption, - req = xhr.responseText; - - //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){ - lineSeparator = '\r'; - } else { - lineSeparator = '\n'; - } - } - - captions = req.split(lineSeparator+lineSeparator); - - for (var r = 0; r < captions.length; r++) { - caption = captions[r]; - plyr.captions[r] = []; - - // Get the parts of the captions - var parts = caption.split(lineSeparator), - index = 0; - - // Incase caption numbers are added - if (parts[index].indexOf(":") === -1) { - index = 1; - } - - plyr.captions[r] = [parts[index], parts[index + 1]]; - } - - // Remove first element ('VTT') - plyr.captions.shift(); - - _log('Successfully loaded the caption file via AJAX'); - } else { - _warn(config.logPrefix + 'There was a problem loading the caption file via AJAX'); - } - } - }; - - xhr.open('get', captionSrc, true); - - xhr.send(); + // 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); } } } - } - - // Set the current caption - function _setCaption(caption) { - /* jshint unused:false */ - var container = _getElement(config.selectors.captions), - content = document.createElement('span'); - // Empty the container - container.innerHTML = ''; + // Set available languages in list + setCaptionsMenu(); + } - // Default to empty - if (_is.undefined(caption)) { - caption = ''; + // Get current selected caption language + function getLanguage() { + if (!support.textTracks || utils.is.empty(player.captions.tracks)) { + return 'No Subs'; } - // Set the span content - if (_is.string(caption)) { - content.innerHTML = caption.trim(); + if (player.captions.enabled) { + return player.captions.currentTrack.label; } else { - content.appendChild(caption); + return 'Disabled'; } - - // Set new caption text - container.appendChild(content); - - // Force redraw (for Safari) - var redraw = container.offsetHeight; } - // Captions functions - // Seek the manual caption time and update UI - 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++) { - // 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"); - } - return _subTcSecs(tcpair[pos]); + // Display active caption if it contains text + function setActiveCue(track) { + // Get the track from the event if needed + if (utils.is.event(track)) { + track = track.target; } - function _timecodeMin(tc) { - return _timecodeCommon(tc, 0); - } - function _timecodeMax(tc) { - return _timecodeCommon(tc, 1); - } - function _subTcSecs(tc) { - if (tc === null || tc === undefined) { - return 0; - } else { - var tc1 = [], - tc2 = [], - seconds; - tc1 = tc.split(','); - tc2 = tc1[0].split(':'); - seconds = Math.floor(tc2[0]*60*60) + Math.floor(tc2[1]*60) + Math.floor(tc2[2]); - return seconds; - } + + var active = track.activeCues[0]; + + // Display a cue, if there is one + if (utils.is.cue(active)) { + setCaption(active.getCueAsHTML()); + } else { + setCaption(); } + } - // If it's not video, or we're using textTracks, bail. - if (plyr.usingTextTracks || plyr.type !== 'video' || !plyr.supported.full) { - return; + // Select active caption + function setLanguage(language) { + // Save config + if (utils.is.string(language)) { + config.captions.language = language.toLowerCase(); + } else if (utils.is.event(language)) { + config.captions.language = language.target.value.toLowerCase(); } - // Reset subcount - plyr.subcount = 0; + // Clear caption + setCaption(); - // 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; + // Re-run setup + setupCaptions(); + } - // If there's no subs available, bail - if (!plyr.captions[plyr.subcount]) { - return; - } + // Set the current caption + function setCaption(caption) { + if (utils.is.htmlElement(player.elements.captions)) { + var content = utils.createElement('span'); - 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; - break; + // Empty the container + utils.emptyElement(player.elements.captions); + + // Default to empty + if (utils.is.undefined(caption)) { + caption = ''; } - } - // 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]; + // Set the span content + if (utils.is.string(caption)) { + content.textContent = caption.trim(); + } else { + content.appendChild(caption); + } + + // Set new caption text + player.elements.captions.appendChild(content); - // Render the caption - _setCaption(plyr.currentCaption); + // Force redraw (for Safari) + // var redraw = captions.offsetHeight; } else { - _setCaption(); + warn('No captions element to render to'); } } // 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); - // 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)) { - active = config.captions.defaultActive; + if (!utils.is.boolean(active)) { + active = config.captions.active; + } else { + config.captions.active = active; } if (active) { - _toggleClass(plyr.container, config.classes.captions.active, true); - _toggleState(plyr.buttons.captions, true); + utils.toggleClass(player.elements.container, config.classes.captions.active, true); + utils.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 (!utils.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; - 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(); - } - } - } + // Toggle state + utils.toggleState(player.elements.buttons.captions, player.captions.enabled); - // Bind the handler - _on(plyr.container, 'keydown', _checkFocus); - } + // Add class hook + utils.toggleClass(player.elements.container, config.classes.captions.active, 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]); - } - } + // 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)' : '')); - loadSprite(iconUrl.url, "sprite-plyr"); + log('AJAX loading absolute SVG sprite' + (player.browser.isIE ? ' (due to IE)' : '')); + utils.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(); + // Larger overlaid play button + if (utils.inArray(config.controls, 'play-large')) { + player.elements.buttons.playLarge = createButton('play-large'); + player.elements.container.appendChild(player.elements.buttons.playLarge); } - // Replace seek time instances - html = _replaceAll(html, '{seektime}', config.seekTime); + // Create a unique ID + player.id = Math.floor(Math.random() * 10000); + + // Null by default + var controls = null; - // Replace all id references with random numbers - html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000))); + // HTML passed as the option + if (utils.is.string(config.controls)) { + controls = config.controls; + } + // A custom function to build controls + // The function can return a HTMLElement or String + else if (utils.is.function(config.controls)) { + controls = config.controls({ + id: player.id, + seektime: config.seekTime + }); + } + // Create controls + else { + controls = createControls({ + id: player.id, + seektime: config.seekTime, + speed: getSpeed(), + // TODO: Get current quality + quality: 'HD', + captions: getLanguage(), + // TODO: Get loop + loop: 'None' + }); + } // Controls container var target; // Inject to custom location - if (_is.string(config.selectors.controls.container)) { + if (utils.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 (!utils.is.htmlElement(target)) { + target = player.elements.container } // Inject controls HTML - target.insertAdjacentHTML('beforeend', html); + if (utils.is.htmlElement(controls)) { + target.appendChild(controls); + } else { + target.insertAdjacentHTML('beforeend', controls); + } + + // Find the elements if need be + if (utils.is.htmlElement(player.elements.controls)) { + findElements(); + } // 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); + utils.toggleClass(label, config.classes.hidden, false); + utils.toggleClass(label, config.classes.tooltip, true); } } } - // Find the UI controls and store references - function _findElements() { + // Find the UI controls and store references in custom controls + // TODO: Allow settings menus with custom controls (coming soon!) + 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), + mute: getElement(config.selectors.buttons.mute), + pip: getElement(config.selectors.buttons.pip), + airplay: getElement(config.selectors.buttons.airplay), + settings: getElement(config.selectors.buttons.settings), + captions: getElement(config.selectors.buttons.captions), + fullscreen: getElement(config.selectors.buttons.fullscreen) + }; // 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 (utils.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) { + // Log it + 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() { + utils.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 && utils.inArray(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 (utils.is.string(config.title) && !utils.is.empty(config.title)) { 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 (utils.is.htmlElement(player.elements.buttons.play)) { + player.elements.buttons.play.setAttribute('aria-label', label); + } + if (utils.is.htmlElement(player.elements.buttons.playLarge)) { + player.elements.buttons.playLarge.setAttribute('aria-label', label); } } // Set iframe title - // https://github.com/sampotts/plyr/issues/124 - if (_is.htmlElement(iframe)) { - iframe.setAttribute('title', config.i18n.frameTitle.replace('{title}', config.title)); + // https://github.com/Selz/plyr/issues/124 + if (utils.is.htmlElement(iframe)) { + var title = utils.is.string(config.title) && !utils.is.empty(config.title) ? config.title : 'video'; + iframe.setAttribute('title', config.i18n.frameTitle.replace('{title}', 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; } // Clean up old volume - // https://github.com/sampotts/plyr/issues/171 + // https://github.com/Selz/plyr/issues/171 window.localStorage.removeItem('plyr-volume'); // load value from the current key @@ -1446,264 +2403,277 @@ 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/sampotts/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); + utils.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); + utils.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 (utils.inArray(types.embed, player.type)) { + utils.toggleClass(player.elements.container, config.classes.type.replace('{0}', 'video'), true); } + // Check for picture-in-picture support + utils.toggleClass(player.elements.container, config.classes.pip.enabled, support.pip && player.type === 'video'); + + // Check for airplay support + utils.toggleClass(player.elements.container, config.classes.airplay.enabled, support.airplay && utils.inArray(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); + utils.toggleClass(player.elements.container, config.classes.stopped, config.autoplay); // Add iOS class - _toggleClass(plyr.container, config.classes.isIos, plyr.browser.isIos); + utils.toggleClass(player.elements.container, config.classes.isIos, player.browser.isIos); // Add touch class - _toggleClass(plyr.container, config.classes.isTouch, plyr.browser.isTouch); - - // Inject the player wrapper - if (plyr.type === 'video') { - // Create the wrapper div - var wrapper = document.createElement('div'); - wrapper.setAttribute('class', config.classes.videoWrapper); + utils.toggleClass(player.elements.container, config.classes.isTouch, support.touch); + } - // Wrap the video in a container - _wrap(plyr.media, wrapper); + // Inject the player wrapper + if (utils.inArray(['video', 'youtube', 'vimeo'], player.type)) { + // Create the wrapper div + player.elements.wrapper = utils.createElement('div', { + class: config.classes.videoWrapper + }); - // Cache the container - plyr.videoContainer = wrapper; - } + // Wrap the video in a container + utils.wrap(player.elements.media, player.elements.wrapper); } // Embeds - if (_inArray(config.types.embed, plyr.type)) { - _setupEmbed(); + if (utils.inArray(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 = utils.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); - break; - - case 'vimeo': - mediaId = _parseVimeoId(plyr.embedId); + mediaId = utils.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]); + utils.removeElement(containers[i]); } // Add embed class for responsive - _toggleClass(plyr.media, config.classes.videoWrapper, true); - _toggleClass(plyr.media, config.classes.embedWrapper, true); - - if (plyr.type === 'youtube') { - // Create the YouTube container - plyr.media.appendChild(container); + utils.toggleClass(player.elements.wrapper, config.classes.embedWrapper, true); + if (player.type === 'youtube') { // Set ID - container.setAttribute('id', id); + player.elements.media.setAttribute('id', id); // Setup API - if (_is.object(window.YT)) { - _youTubeReady(mediaId, container); + if (utils.is.object(window.YT)) { + youTubeReady(mediaId); } else { // Load the API - _injectScript(config.urls.youtube.api); + utils.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); + }); // 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') { - // Vimeo needs an extra div to hide controls on desktop (which has full support) - if (plyr.supported.full) { - plyr.media.appendChild(container); - } else { - container = plyr.media; - } - + } else if (player.type === 'vimeo') { // Set ID - container.setAttribute('id', id); + player.elements.media.setAttribute('id', id); // Load the API if not already - if (!_is.object(window.Vimeo)) { - _injectScript(config.urls.vimeo.api); + if (!utils.is.object(window.Vimeo)) { + utils.injectScript(config.urls.vimeo.api); // Wait for fragaloop load var vimeoTimer = window.setInterval(function() { - if (_is.object(window.Vimeo)) { + if (utils.is.object(window.Vimeo)) { window.clearInterval(vimeoTimer); - _vimeoReady(mediaId, container); + vimeoReady(mediaId); } }, 50); } else { - _vimeoReady(mediaId, container); + vimeoReady(mediaId); } - } 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 = utils.createElement('iframe'); // Watch for iframe load soundCloud.loaded = false; - _on(soundCloud, 'load', function() { soundCloud.loaded = true; }); + utils.on(soundCloud, 'load', function() { + soundCloud.loaded = true; + }); - _setAttributes(soundCloud, { - 'src': 'https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/' + mediaId, - 'id': id + utils.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(soundCloud); // Load the API if not already if (!window.SC) { - _injectScript(config.urls.soundcloud.api); + utils.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) { // Setup instance // https://developers.google.com/youtube/iframe_api_reference - plyr.embed = new window.YT.Player(container.id, { + player.embed = new window.YT.Player(player.elements.media.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_load_policy: (config.captions.active ? 1 : 0), + 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, + playsinline: 1, + origin: window.location.href }, 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.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); @@ -1711,22 +2681,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); }, @@ -1746,48 +2716,60 @@ // 5 Video cued switch (event.data) { case 0: - plyr.media.paused = true; - _triggerEvent(plyr.media, 'ended'); + // YouTube doesn't support loop for a single video, so mimick it. + if (config.loop.active) { + // YouTube needs a call to `stopVideo` before playing again + instance.stopVideo(); + instance.playVideo(); + break; + } + 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/sampotts/plyr/issues/374 + // 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 }); } @@ -1796,235 +2778,370 @@ } // Vimeo ready - function _vimeoReady(mediaId, container) { + function vimeoReady(mediaId) { // Setup instance // https://github.com/vimeo/player.js - plyr.embed = new window.Vimeo.Player(container, { - id: parseInt(mediaId), - loop: config.loop, - autoplay: config.autoplay, - byline: false, - portrait: false, - title: false + player.embed = new window.Vimeo.Player(player.elements.media, { + id: mediaId, + loop: config.loop.active, + autoplay: config.autoplay, + byline: false, + portrait: false, + title: false }); // 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'); - }*/ + // Get captions + player.embed.getTextTracks().then(function(tracks) { + // tracks = an array of track objects + setupCaptions(tracks); - plyr.embed.on('loaded', function() { + // TODO: Captions + if (config.captions.active) { + player.embed.enableTextTrack(config.captions.language.toLowerCase()); + } + }); + + player.embed.on('cuechange', function(data) { + var cue = null; + + if (data.cues.length) { + cue = utils.stripHTML(data.cues[0].text); + } + + setCaption(cue); + }); + + player.embed.on('loaded', function() { // Fix keyboard focus issues - // https://github.com/sampotts/plyr/issues/317 - if (_is.htmlElement(plyr.embed.element) && plyr.supported.full) { - plyr.embed.element.setAttribute('tabindex', '-1'); + // https://github.com/Selz/plyr/issues/317 + if (utils.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 (!utils.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 (!utils.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 = utils.is.number(config.loop.start) && utils.is.number(config.loop.end); + var start = updateTimeDisplay(config.loop.start, getElement('[data-plyr-loop="start"]')); + var end = null; + + if (utils.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 (utils.is.event(speed)) { + speed = parseFloat(speed.target.value); + } else if (!utils.is.number(speed)) { + speed = parseFloat(player.storage.speed || config.speed.selected); + } + + // Set min/max + if (speed < 0.1) { + speed = 0.1; + } + if (speed > 2.0) { + speed = 2.0; + } + + if (!utils.is.array(config.speed.options)) { + warn('Invalid speeds format'); + return; + } + + // Store current speed + config.speed.selected = speed; + + // Set HTML5 speed + // TODO: set YouTube + player.elements.media.playbackRate = speed; + + // Save speed to localStorage + updateStorage({ + speed: speed + }); + } + + // Get the current speed value + function getSpeed() { + return config.speed.selected.toFixed(1).toString().replace('.0', '') + '×' + } + // Rewind - function _rewind(seekTime) { + function rewind(seekTime) { // Use default if needed - if (!_is.number(seekTime)) { + if (!utils.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 (!utils.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 (utils.is.number(input)) { targetTime = input; - } else if (_is.object(input) && _inArray(['input', 'change'], input.type)) { + } else if (utils.is.event(input) && utils.inArray(['input', 'change'], input.type)) { // It's the seek slider // Seek to the selected time targetTime = ((input.target.value / input.target.max) * duration); @@ -2038,64 +3155,60 @@ } // 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 (utils.inArray(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'); - - // Special handling for 'manual' captions - _seekManualCaptions(targetTime); + log('Seeking to ' + player.elements.media.currentTime + ' seconds'); } // 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 @@ -2103,16 +3216,16 @@ } // Check playing state - function _checkPlaying() { - _toggleClass(plyr.container, config.classes.playing, !plyr.media.paused); + function checkPlaying() { + utils.toggleClass(player.elements.container, config.classes.playing, !player.elements.media.paused); - _toggleClass(plyr.container, config.classes.stopped, plyr.media.paused); + utils.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 @@ -2120,110 +3233,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); + utils.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) { + utils.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 (!utils.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; + utils.removeElement(clone); + } + + target.setAttribute('aria-hidden', !show); + toggle.setAttribute('aria-expanded', show); + target.removeAttribute('tabindex'); + + 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 (!utils.is.boolean(muted)) { + muted = !player.elements.media.muted; } // Set button state - _toggleState(plyr.buttons.mute, muted); + utils.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 (utils.inArray(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 (utils.is.event(volume)) { + volume = volume.target.value; + } // Load volume from storage if no value specified - if (_is.undefined(volume)) { - volume = plyr.storage.volume; + if (utils.is.undefined(volume)) { + volume = player.storage.volume; } // Use config if all else fails @@ -2241,117 +3419,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 (utils.inArray(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 (!utils.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 (!utils.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)); + utils.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)); + if (player.supported.full && player.elements.buttons.mute) { + utils.toggleState(player.elements.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 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 @@ -2360,52 +3512,52 @@ // Timer to prevent flicker when seeking timers.loading = setTimeout(function() { // Toggle container class hook - _toggleClass(plyr.container, config.classes.loading, loading); + utils.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 = utils.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 utils.getPercentage(buffered.end(0), duration); + } else if (utils.is.number(buffered)) { // YouTube returns between 0 and 1 return (buffered * 100); } @@ -2417,45 +3569,46 @@ } } - // Set values - _setProgress(progress, value); + if (utils.is.number(config.loop.start) && utils.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 (utils.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 (utils.is.undefined(progress)) { + if (utils.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 (utils.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 (utils.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; @@ -2466,102 +3619,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 (!utils.is.number(time)) { time = 0; } - var duration = _getDuration(), - value = _getPercentage(time, duration); + var duration = getDuration(), + value = utils.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 || !utils.is.htmlElement(player.elements.inputs.seek) || !utils.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 (utils.is.event(event)) { + percent = ((100 / clientRect.width) * (event.pageX - clientRect.left)); + } else { + if (utils.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 @@ -2572,41 +3731,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 (utils.is.event(event) && utils.inArray(['mouseenter', 'mouseleave'], event.type)) { + utils.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 = utils.hasClass(player.elements.container, config.classes.loading); // Default to false if no boolean - if (!_is.boolean(toggle)) { + if (!utils.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 = utils.inArray(['mousemove', 'touchstart', 'mouseenter', 'focus'], toggle.type); // Delay hiding on move events - if (_inArray(['mousemove', 'touchmove'], toggle.type)) { + if (utils.inArray(['mousemove', 'touchmove'], toggle.type)) { delay = 2000; } @@ -2615,7 +3774,7 @@ delay = 3000; } } else { - show = _hasClass(plyr.container, config.classes.hideControls); + show = utils.hasClass(player.elements.container, config.classes.hideControls); } } @@ -2623,63 +3782,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) { + utils.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 (support.touch) { 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); + utils.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 (!utils.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; } @@ -2688,202 +3847,188 @@ // 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 (!utils.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); + utils.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; + // Reset embed object + player.embed = null; + + // Remove media + removeElement('media'); - // Remove the old media - _remove(plyr.media); + // Remove the old captions + removeElement('captions'); // Remove video container - if (plyr.type === 'video' && plyr.videoContainer) { - _remove(plyr.videoContainer); - } + removeElement('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 && utils.inArray(types.embed, firstSource.type)) { + player.type = firstSource.type; } } } // Check for support - plyr.supported = supported(plyr.type); + player.supported = utils.checkSupport(player.type, config.inline); // Create new markup - switch(plyr.type) { + switch (player.type) { case 'video': - plyr.media = document.createElement('video'); + player.elements.media = utils.createElement('video'); break; case 'audio': - plyr.media = document.createElement('audio'); + player.elements.media = utils.createElement('audio'); break; case 'youtube': case 'vimeo': case 'soundcloud': - plyr.media = document.createElement('div'); - plyr.embedId = source.sources[0].src; + player.elements.media = utils.createElement('div'); + player.embedId = source.sources[0].src; break; } // Inject the new element - _prependChild(plyr.container, plyr.media); + utils.prependChild(player.elements.container, player.elements.media); // Autoplay the new source? - if (_is.boolean(source.autoplay)) { + if (utils.is.boolean(source.autoplay)) { config.autoplay = source.autoplay; } // Set attributes for audio and video - if (_inArray(config.types.html5, plyr.type)) { + if (utils.inArray(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', ''); + } + if (config.inline) { + player.elements.media.setAttribute('playsinline', ''); } } // Restore class hooks - _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen); - _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled); - _toggleStyleHook(); + utils.toggleClass(player.elements.container, config.classes.fullscreen.active, player.fullscreen.active); + utils.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 (utils.inArray(types.html5, player.type)) { + insertElements('source', source.sources); } // Set up from scratch - _setupMedia(); + setupMedia(); // HTML5 stuff - if (_inArray(config.types.html5, plyr.type)) { + if (utils.inArray(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 (utils.inArray(types.html5, player.type) || (utils.inArray(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 = utils.hasClass(trigger, config.classes.tabFocus); setTimeout(function() { - target.focus(); + if (utils.is.htmlElement(target)) { + target.focus(); + } if (hadTabFocus) { - _toggleClass(trigger, config.classes.tabFocus, false); - _toggleClass(target, config.classes.tabFocus, true); + utils.toggleClass(trigger, config.classes.tabFocus, false); + utils.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; @@ -2891,84 +4036,84 @@ // Detect tab focus function checkTabFocus(focused) { - for (var button in plyr.buttons) { - var element = plyr.buttons[button]; + utils.toggleClass(getElements('.' + config.classes.tabFocus), config.classes.tabFocus, false); - if (_is.nodeList(element)) { - for (var i = 0; i < element.length; i++) { - _toggleClass(element[i], config.classes.tabFocus, (element[i] === focused)); - } - } else { - _toggleClass(element, config.classes.tabFocus, (element === focused)); - } + if (player.elements.container.contains(focused)) { + utils.toggleClass(focused, config.classes.tabFocus, true); } } // 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; - - // Only handle global key press if there's only one player - // and the key is in the allowed keys + if (config.keyboardShortcuts.global) { + utils.on(window, 'keydown keyup', function(event) { + var code = getKeyCode(event); + var focused = utils.getFocusElement(); + var allowed = [48, 49, 50, 51, 52, 53, 54, 56, 57, 75, 77, 70, 67, 73, 76, 79]; + + // Only handle global key press if 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 (utils.inArray(allowed, code) && (!utils.is.htmlElement(focused) || !utils.matches(focused, config.selectors.editable))) { handleKey(event); } - }); + }, false); } // Handle presses on focused - _on(plyr.container, 'keydown keyup', handleKey); + utils.on(player.elements.container, 'keydown keyup', handleKey, false); } 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 (!utils.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 (!utils.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 (utils.inArray(checkFocus, code)) { + var focused = utils.getFocusElement(); + + if (utils.is.htmlElement(focused) && utils.getFocusElement().type === "radio") { + return; + } + } // If the code is found prevent default (e.g. prevent scrolling for arrows) - if (_inArray(preventDefault, code)) { + if (utils.inArray(preventDefault, code)) { event.preventDefault(); event.stopPropagation(); } - switch(code) { - // 0-9 + switch (code) { case 48: case 49: case 50: @@ -2978,30 +4123,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 @@ -3012,157 +4204,238 @@ } // Focus/tab management - _on(window, 'keyup', function(event) { - var code = getKeyCode(event), - focused = getFocusElement(); + utils.on(window, 'keyup', function(event) { + var code = getKeyCode(event); + var focused = utils.getFocusElement(); if (code === 9) { checkTabFocus(focused); } }); - _on(document.body, 'click', function() { - _toggleClass(_getElement('.' + config.classes.tabFocus), config.classes.tabFocus, false); + utils.on(document.body, 'click', function() { + utils.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); + utils.on(element, 'blur', function() { + utils.toggleClass(element, 'tab-focus', false); }); } + // Trigger custom and default handlers + var handlerProxy = function(event, customHandler, defaultHandler) { + if (utils.is.function(customHandler)) { + customHandler.call(this, event); + } + if (utils.is.function(defaultHandler)) { + defaultHandler.call(this, event); + } + } + // Play - _proxyListener(plyr.buttons.play, 'click', config.listeners.play, togglePlay); + utils.proxy(player.elements.buttons.play, 'click', config.listeners.play, _togglePlay); + utils.proxy(player.elements.buttons.playLarge, 'click', config.listeners.play, _togglePlay); // Pause - _proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, togglePlay); + utils.proxy(player.elements.buttons.pause, 'click', config.listeners.pause, _togglePlay); - // Restart - _proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek); + // Pause + utils.proxy(player.elements.buttons.restart, 'click', config.listeners.restart, seek); // Rewind - _proxyListener(plyr.buttons.rewind, 'click', config.listeners.rewind, _rewind); + utils.proxy(player.elements.buttons.rewind, 'click', config.listeners.rewind, rewind); - // Fast forward - _proxyListener(plyr.buttons.forward, 'click', config.listeners.forward, _forward); + // Rewind + utils.proxy(player.elements.buttons.forward, 'click', config.listeners.forward, forward); - // Seek - _proxyListener(plyr.buttons.seek, inputEvent, config.listeners.seek, _seek); + // Mute + utils.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 + utils.proxy(player.elements.buttons.captions, 'click', config.listeners.captions, toggleCaptions); + + // Fullscreen + utils.proxy(player.elements.buttons.fullscreen, 'click', config.listeners.fullscreen, toggleFullscreen); + + // Picture-in-Picture + utils.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 + utils.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 + utils.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 + utils.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 + utils.on(player.elements.settings.menu, 'click', function(event) { + // Settings - Language + if (utils.matches(event.target, config.selectors.inputs.language)) { + handlerProxy.call(this, event, config.listeners.language, setLanguage); + } + + // Settings - Quality + else if (utils.matches(event.target, config.selectors.inputs.quality)) { + handlerProxy.call(this, event, config.listeners.quality, function() { + warn("Set quality"); + }); + } + + // Settings - Speed + else if (utils.matches(event.target, config.selectors.inputs.speed)) { + handlerProxy.call(this, event, config.listeners.speed, setSpeed); + } + + // Settings - Looping + // TODO: use toggle buttons + else if (utils.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 (utils.inArray(['start', 'end', 'all', 'none'], value)) { + toggleLoop(value); + } + }); + } + }); + + // Seek + utils.proxy(player.elements.inputs.seek, inputEvent, config.listeners.seek, seek); + + // Seek + utils.proxy(player.elements.inputs.volume, inputEvent, config.listeners.volume, setVolume); // Seek tooltip - _on(plyr.progress.container, 'mouseenter mouseleave mousemove', _updateSeekTooltip); + utils.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); + utils.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'; + utils.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 + utils.on(player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function(event) { + player.elements.controls.pressed = utils.inArray(['mousedown', 'touchstart'], event.type); }); // Focus in/out on controls - _on(plyr.controls, 'focus blur', _toggleControls, true); + // TODO: Check we need capture here + utils.on(player.elements.controls, 'focus blur', toggleControls, true, true); } - // Adjust volume on scroll - _on(plyr.volume.input, 'wheel', function(event) { - event.preventDefault(); - + // Mouse wheel for volume + utils.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(); + } + }, false); + + // Handle user exiting fullscreen by escaping etc + if (support.fullscreen) { + utils.on(document, fullscreen.eventType, toggleFullscreen); + } } // Listen for media events - function _mediaListeners() { + function mediaListeners() { // Time change on media - _on(plyr.media, 'timeupdate seeking', _timeUpdate); - - // Update manual captions - _on(plyr.media, 'timeupdate', _seekManualCaptions); + utils.on(player.elements.media, 'timeupdate seeking', timeUpdate); // Display duration - _on(plyr.media, 'durationchange loadedmetadata', _displayDuration); + utils.on(player.elements.media, 'durationchange loadedmetadata', displayDuration); // Handle the media finishing - _on(plyr.media, 'ended', function() { + utils.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); + utils.on(player.elements.media, 'progress playing', updateProgress); // Handle native mute - _on(plyr.media, 'volumechange', _updateVolume); + utils.on(player.elements.media, 'volumechange', updateVolume); // Handle native play/pause - _on(plyr.media, 'play pause ended', _checkPlaying); + utils.on(player.elements.media, 'play pause ended', checkPlaying); // Loading - _on(plyr.media, 'waiting canplay seeked', _checkLoading); + utils.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) { @@ -3173,80 +4446,77 @@ wrapper.style.cursor = "pointer"; // On click play, pause ore restart - _on(wrapper, 'click', function() { + utils.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 && support.touch && !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(); }); + utils.on(player.elements.media, 'contextmenu', function(event) { + event.preventDefault(); + }, false); } // 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); + utils.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/sampotts/plyr/issues/174 - function _cancelRequests() { - if (!_inArray(config.types.html5, plyr.type)) { + // See https://github.com/Selz/plyr/issues/174 + function cancelRequests() { + if (!utils.inArray(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]); + utils.removeElement(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/sampotts/plyr/issues/174 - plyr.media.load(); + // See https://github.com/Selz/plyr/issues/174 + 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) { - // Bail if the element is not initialized - if (!plyr.init) { - return null; - } - + function destroy(callback, restore) { // 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(); @@ -3256,17 +4526,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(); @@ -3275,16 +4545,14 @@ } function cleanUp() { - clearTimeout(timers.cleanUp); - // Default to restore original element - if (!_is.boolean(restore)) { + if (!utils.is.boolean(restore)) { restore = true; } // Callback - if (_is.function(callback)) { - callback.call(original); + if (utils.is.function(callback)) { + callback.call(player.original); } // Bail if we don't need to restore the original element @@ -3292,485 +4560,296 @@ return; } - // Remove init flag - plyr.init = false; - // Replace the container with the original element provided - plyr.container.parentNode.replaceChild(original, plyr.container); + player.elements.container.parentNode.replaceChild(player.original, player.elements.container); - // Allow overflow (set on fullscreen) + // Reset overflow (incase destroyed while fullscreen) document.body.style.overflow = ''; // Event - _triggerEvent(original, 'destroyed', true); + trigger(player.original, 'destroyed', true); } } - // Setup a player - function _init() { - // Bail if the element is initialized - if (plyr.init) { - return null; - } - - // Setup the fullscreen api - fullscreen = _fullscreen(); - - // Sniff out the browser - plyr.browser = _browserSniff(); - - // Bail if nothing to setup - if (!_is.htmlElement(plyr.media)) { - return; - } - - // Load saved settings from localStorage - _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'); - - // 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)); - } - - // Check for support - plyr.supported = supported(plyr.type); - - // If no native support, bail - if (!plyr.supported.basic) { - return; - } - - // Wrap media - plyr.container = _wrap(media, document.createElement('div')); - - // Allow focus to be captured - plyr.container.setAttribute('tabindex', 0); - - // Add style hook - _toggleStyleHook(); - - // Debug info - _log('' + plyr.browser.name + ' ' + plyr.browser.version); - - // Setup media - _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)) { - // Setup UI - _setupInterface(); - - // Call ready - _ready(); - - // Set title on button and frame - _setTitle(); - } - - // Successful setup - plyr.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)); + removeElement('controls'); // Remove large play - _remove(_getElement(config.selectors.buttons.play)); + removeElement('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 (!utils.is.htmlElement(player.elements.controls)) { // Inject custom controls - _injectControls(); - } + injectControls(); - // Find the elements - if (!_findElements()) { - return; + // Re-attach listeners + controlListeners(); } - // If the controls are injected, re-bind listeners for controls - if (controlsMissing) { - _controlListeners(); + // If there's no controls, bail + if (!utils.is.htmlElement(player.elements.controls)) { + return; } // Media element listeners - _mediaListeners(); + mediaListeners(); // Remove native controls - _toggleNativeControls(); + toggleNativeControls(); // Setup fullscreen - _setupFullscreen(); + setupFullscreen(); // Captions - _setupCaptions(); + setupCaptions(); // 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 - }; - // Everything done - function _ready() { - // Ready event at end of execution stack - window.setTimeout(function() { - _triggerEvent(plyr.media, 'ready'); - }, 0); - + function ready() { // Set class hook on media element - _toggleClass(plyr.media, defaults.classes.setup, true); + // utils.toggleClass(player.elements.media, defaults.classes.setup, true); // Set container class for ready - _toggleClass(plyr.container, config.classes.ready, true); + // utils.toggleClass(player.elements.container, config.classes.ready, true); // Store a refernce to instance - plyr.media.plyr = api; + // player.elements.media.plyr = api; + + // Ready event at end of execution stack + trigger(player.elements.container, 'ready', true); // Autoplay if (config.autoplay) { - _play(); + play(); } } - // Initialize instance - _init(); - - // If init failed, return null - if (!plyr.init) { - return null; - } - - return api; - } - - // Load a sprite - function loadSprite(url, id) { - var x = new XMLHttpRequest(); - - // If the id is set and sprite exists, bail - if (_is.string(id) && _is.htmlElement(document.querySelector('#' + id))) { - return; - } - - // Create placeholder (to prevent loading twice) - var container = document.createElement('div'); - container.setAttribute('hidden', ''); - if (_is.string(id)) { - container.setAttribute('id', id); - } - document.body.insertBefore(container, document.body.childNodes[0]); - - // Check for CORS support - if ('withCredentials' in x) { - x.open('GET', url, true); - } else { - return; - } - - // Inject hidden div with sprite on load - x.onload = function() { - container.innerHTML = x.responseText; - } - - x.send(); - } - - // 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; - - switch (type) { - case 'video': - basic = videoSupport; - full = (basic && (!isOldIE && !isIphone)); - break; - - case 'audio': - basic = audioSupport; - 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; - } + // Setup a player + function setup(target) { + // We need an element to setup + if (!utils.is.htmlElement(target)) { + error('Setup failed. No suitable element passed.'); + return false; + } - break; + // Bail if not enabled + if (!config.enabled) { + return false; + } - case 'soundcloud': - basic = true; - full = (!isOldIE && !isIphone); - break; + // Bail if disabled or no basic support + // You may want to disable certain UAs etc + if (!utils.checkSupport().basic) { + return false; + } - default: - basic = (audioSupport && videoSupport); - full = (basic && !isOldIE); - } + // Bail if the element is initialized + if (target.plyr) { + return false; + } - return { - basic: basic, - full: full - }; - } + // Set media type based on tag or data attribute + // Supported: video, audio, vimeo, youtube + var type = target.tagName.toLowerCase(); - // Setup function - function setup(targets, options) { - // Get the players - var players = [], - instances = [], - selector = [defaults.selectors.html5, defaults.selectors.embed].join(','); + // Different setup based on type + switch (type) { + case 'div': + player.type = target.getAttribute('data-type'); + player.embedId = target.getAttribute('data-video-id'); - // Select the elements - if (_is.string(targets)) { - // String selector passed - targets = document.querySelectorAll(targets); - } else if (_is.htmlElement(targets)) { - // Single HTMLElement passed - targets = [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)) { - options = targets; - } + if (utils.is.empty(player.type) || utils.is.empty(player.embedId)) { + return false; + } - // Use default selector - targets = document.querySelectorAll(selector); - } + // Clean up + target.removeAttribute('data-type'); + target.removeAttribute('data-video-id'); + break; - // Convert NodeList to array - if (_is.nodeList(targets)) { - targets = Array.prototype.slice.call(targets); - } + case 'iframe': + // Do something with the iframe + break; - // Bail if disabled or no basic support - // You may want to disable certain UAs etc - if (!supported().basic || !targets.length) { - return false; - } + case 'video': + case 'audio': + player.type = type; + config.crossorigin = target.getAttribute('crossorigin') !== null; + config.autoplay = config.autoplay || (target.getAttribute('autoplay') !== null); + config.inline = target.getAttribute('playsinline') !== null; + config.loop.active = config.loop || (target.getAttribute('loop') !== null); + break; - // Add to container list - function add(target, media) { - if (!_hasClass(media, defaults.classes.hook)) { - players.push({ - // Always wrap in a <div> for styling - //container: _wrap(media, document.createElement('div')), - // Could be a container or the media itself - target: target, - // This should be the <video>, <audio> or <div> (YouTube/Vimeo) - media: media - }); + default: + return false; } - } - - // Check if the targets have multiple media elements - for (var i = 0; i < targets.length; i++) { - var target = targets[i]; - // Get children - var children = target.querySelectorAll(selector); + // Sniff out the browser + player.browser = utils.getBrowser(); - // If there's more than one media element child, wrap them - if (children.length) { - for (var x = 0; x < children.length; x++) { - add(target, children[x]); - } - } else if (_matches(target, selector)) { - // Target is media element - add(target, target); - } - } + // Load saved settings from localStorage + setupStorage(); - // Create a player instance for each element - players.forEach(function(player) { - var element = player.target, - media = player.media, - match = false; + // Check for support + player.supported = utils.checkSupport(player.type, config.inline); - // The target element can also be the media element - if (media === element) { - match = true; + // If no native support, bail + if (!player.supported.basic) { + return false; } - // Setup a player instance and add to the element - // Create instance-specific config - var data = {}; + // Wrap media + player.elements.container = utils.wrap(target, utils.createElement('div')); - // Try parsing data attribute config - try { data = JSON.parse(element.getAttribute('data-plyr')); } - catch(e) { } + // Cache original element state for .destroy() + player.original = target.cloneNode(true); - var config = _extend({}, defaults, options, data); + // Allow focus to be captured + player.elements.container.setAttribute('tabindex', 0); - // Bail if not enabled - if (!config.enabled) { - return null; - } + // Add style hook + toggleStyleHook(); - // Create new instance - var instance = new Plyr(media, config); + // Debug info + log(player.browser.name + ' ' + player.browser.version); - // Go to next if setup failed - if (!_is.object(instance)) { - return; - } + // Setup media + setupMedia(); // Listen for events if debugging if (config.debug) { var events = config.events.concat(['setup', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']); - _on(instance.getContainer(), events.join(' '), function(event) { - console.log([config.logPrefix, 'event:', event.type].join(' '), event.detail.plyr); + utils.on(player.elements.container, events.join(' '), function(event) { + log(['event:', event.type].join(' ').trim()); }); } - // Callback - _event(instance.getContainer(), 'setup', true, { - plyr: instance - }); + // Setup interface + // If embed but not fully supported, setupInterface (to avoid flash of controls) and call ready now + if (utils.inArray(types.html5, player.type) || (utils.inArray(types.embed, player.type) && !player.supported.full)) { + // Setup UI + setupInterface(); - // Add to return array even if it's already setup - instances.push(instance); - }); + // Call ready + ready(); - return instances; - } + // Set title on button and frame + setTitle(); + } - // Get all instances within a provided container - function get(container) { - if (_is.string(container)) { - // Get selector if string passed - container = document.querySelector(container); - } else if (_is.undefined(container)) { - // Use body by default to get all on page - container = document.body; + // Successful setup + return true; } - // If we have a HTML element - 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)) { - instances.push(element.plyr); - } - }); + // Expose prototypes + api = { + getOriginal: function() { + return player.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 utils.hasClass(player.elements.container, config.classes.ready); + }, + isLoading: function() { + return utils.hasClass(player.elements.container, config.classes.loading); + }, + isPaused: function() { + return player.elements.media.paused; + }, + isLooping: function() { + return config.loop.active; + }, + on: function(event, callback) { + utils.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, + setLanguage: setLanguage, + isFullscreen: player.fullscreen.active, + support: function(mimeType) { + return support.mime(player, mimeType); + }, + destroy: destroy + }; - return instances; + // Initialize instance + if (!setup(player.elements.media)) { + return null; } - return []; + // Expose API + return api; } - return { - setup: setup, - supported: supported, - loadSprite: loadSprite, - get: get - }; + return Player; })); - -// Custom event polyfill -// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent -(function () { - if (typeof window.CustomEvent === 'function') { - return; - } - - function CustomEvent(event, params) { - params = params || { bubbles: false, cancelable: false, detail: undefined }; - var evt = document.createEvent('CustomEvent'); - evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); - return evt; - } - - CustomEvent.prototype = window.Event.prototype; - - window.CustomEvent = CustomEvent; -})(); diff --git a/src/less/plyr.less b/src/less/plyr.less index c9e3d7c3..3ffa95fe 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,17 +189,15 @@ position: relative; background: #000; border-radius: inherit; + // Require z-index to force border-radius + z-index: 0; + overflow: hidden; } // Container for embeds .plyr__video-embed { 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; @@ -225,7 +239,6 @@ color: @plyr-captions-color; font-size: @plyr-font-size-captions-base; text-align: center; - font-weight: 400; span { border-radius: 2px; @@ -233,6 +246,11 @@ background: @plyr-captions-bg; box-decoration-break: clone; line-height: 150%; + + // Firefox adds a <div> when using getCueAsHTML() + div { + display: inline; + } } span:empty { display: none; @@ -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 |