plyr-rails 0.1.4 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd87d82f2b7ab076b8598bb5230c1470fe200009
4
- data.tar.gz: ae1238a20b6ea40997037743bb209e8d9b853d27
3
+ metadata.gz: a3a6d134588f8f50c366add75e88f20961ec2341
4
+ data.tar.gz: 5e54eec74f7d3ad368982cf2ec3ef568e0808d08
5
5
  SHA512:
6
- metadata.gz: 19233e702fe6d5f77914eff8c0ba2d3ebd319efff3b5f97328b18a7996d206a8ebb6b4f4fc85ec75d0cbaf19d75e1cc11bd88dfc87cb5ef262a3f91558ad7996
7
- data.tar.gz: b80986220b8eaea9a64ce2fabfdaf22e4e23957088a99872a81ddd5723e88b4f1a668fdcbceb38673f93086a2b9eea8827a591ba87f5c57fcbf509c92fccee25
6
+ metadata.gz: 5e55ef225230fca754950a197fb72b7c5e6af4cbda5d8c331eee9d47b17b77f54a6ea39a7abf7ac8c00c27c2fb89de160a2be58a2f9f0e02bf068e5096ab562d
7
+ data.tar.gz: 0df5c6e34f743755c087012e1348948bf08c1286a45da455eb1fcc5f22578116e7acfaff0ff655ec437e8243411e5425bd55203aaf8028eb91630b2f19b754cf
data/README.md CHANGED
@@ -20,9 +20,33 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install plyr-rails
22
22
 
23
+ Now you need to edit your `app/assets/javascripts/application.js` file and add the following line:
24
+ ``` javascript
25
+ //= require plyr
26
+ ```
27
+
28
+ Now you need to edit your `app/assets/stylesheets/application.css` file and add the following line:
29
+ ``` css
30
+ *= require plyr
31
+ ```
32
+
23
33
  ## Usage
24
34
 
25
- TODO: Write usage instructions here
35
+ To load and intialize the player.
36
+
37
+ You need to add this to your `app/assets/javascripts/application.js` file and add the following line:
38
+ ``` javascript
39
+ $(document).ready(function(){
40
+ plyr.setup();
41
+ });
42
+ ```
43
+
44
+ Add html code to your any view file `app/views/blogs/index.erb` file and add the following code:
45
+ ``` html
46
+ <div class="plyr">
47
+ <div data-video-id="bTqVqk7FSmY" data-type="youtube"></div>
48
+ </div>
49
+ ```
26
50
 
27
51
  ## Development
28
52
 
data/lib/plyr/rails.rb CHANGED
@@ -2,6 +2,26 @@ require "plyr/rails/version"
2
2
 
3
3
  module Plyr
4
4
  module Rails
5
- class Engine < ::Rails::Engine; end
5
+ class Engine < ::Rails::Engine
6
+ initializer :append_dependent_assets_path, :group => :all do |app|
7
+ app.config.assets.paths += %w( sprite )
8
+
9
+ app.config.assets.precompile += %w( plyr.scss )
10
+ app.config.assets.precompile += %w( plyr.js )
11
+
12
+ app.config.assets.precompile += %w( plyr-captions-off.svg )
13
+ app.config.assets.precompile += %w( plyr-captions-on.svg )
14
+ app.config.assets.precompile += %w( plyr-enter-fullscreen.svg )
15
+ app.config.assets.precompile += %w( plyr-exit-fullscreen.svg )
16
+ app.config.assets.precompile += %w( plyr-fast-forward.svg )
17
+ app.config.assets.precompile += %w( plyr-muted.svg )
18
+ app.config.assets.precompile += %w( plyr-pause.svg )
19
+ app.config.assets.precompile += %w( plyr-play.svg )
20
+ app.config.assets.precompile += %w( plyr-restart.svg )
21
+ app.config.assets.precompile += %w( plyr-rewind.svg )
22
+ app.config.assets.precompile += %w( plyr-volume.svg )
23
+
24
+ end
25
+ end
6
26
  end
7
27
  end
@@ -1,5 +1,5 @@
1
1
  module Plyr
2
2
  module Rails
3
- VERSION = "0.1.4"
3
+ VERSION = "2.0.11"
4
4
  end
5
5
  end
@@ -1,2 +1,3775 @@
1
- !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t(e,document):"function"==typeof define&&define.amd?define(null,function(){t(e,document)}):e.plyr=t(e,document)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(){var e,n,a,r=navigator.userAgent,s=navigator.appName,o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10);return-1!==navigator.appVersion.indexOf("Windows NT")&&-1!==navigator.appVersion.indexOf("rv:11")?(s="IE",o="11;"):-1!==(n=r.indexOf("MSIE"))?(s="IE",o=r.substring(n+5)):-1!==(n=r.indexOf("Chrome"))?(s="Chrome",o=r.substring(n+7)):-1!==(n=r.indexOf("Safari"))?(s="Safari",o=r.substring(n+7),-1!==(n=r.indexOf("Version"))&&(o=r.substring(n+8))):-1!==(n=r.indexOf("Firefox"))?(s="Firefox",o=r.substring(n+8)):(e=r.lastIndexOf(" ")+1)<(n=r.lastIndexOf("/"))&&(s=r.substring(e,n),o=r.substring(n+1),s.toLowerCase()==s.toUpperCase()&&(s=navigator.appName)),-1!==(a=o.indexOf(";"))&&(o=o.substring(0,a)),-1!==(a=o.indexOf(" "))&&(o=o.substring(0,a)),i=parseInt(""+o,10),isNaN(i)&&(o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10)),{name:s,version:i,ios:/(iPad|iPhone|iPod)/g.test(navigator.platform),touch:"ontouchstart"in t.documentElement}}function a(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 r(e){if(!t.querySelectorAll('script[src="'+e+'"]').length){var n=t.createElement("script");n.src=e;var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(n,a)}}function s(e,t){return Array.prototype.indexOf&&-1!=e.indexOf(t)}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 a=n>0?t.cloneNode(!0):t,r=e[n],s=r.parentNode,o=r.nextSibling;a.appendChild(r),o?s.insertBefore(a,o):s.appendChild(a)}}function l(e){for(var t=e.parentNode;e.firstChild;)t.insertBefore(e.firstChild,e);t.removeChild(e)}function u(e){e&&e.parentNode.removeChild(e)}function c(e,t){e.insertBefore(t,e.firstChild)}function p(e,t){for(var n in t)e.setAttribute(n,"boolean"==typeof t[n]&&t[n]?"":t[n])}function d(e,n,a){var r=t.createElement(e);p(r,a),c(n,r)}function A(e){return e.replace(".","")}function m(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var a=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=a+(n?" "+t:"")}}function f(e,t){return e?e.classList?e.classList.contains(t):new RegExp("(\\s|^)"+t+"(\\s|$)").test(e.className):!1}function y(e,t,n,a){e&&g(e,t,n,!0,a)}function b(e,t,n,a){e&&g(e,t,n,!1,a)}function v(e,t,n,a,r){y(e,t,function(t){n&&n.apply(e,[t]),a.apply(e,[t])},r)}function g(e,t,n,a,r){var s=t.split(" ");if("boolean"!=typeof r&&(r=!1),e instanceof NodeList)for(var o=0;o<e.length;o++)e[o]instanceof Node&&g(e[o],arguments[1],arguments[2],arguments[3]);else for(var i=0;i<s.length;i++)e[a?"addEventListener":"removeEventListener"](s[i],n,r)}function h(e,t,n,a){if(e&&t){"boolean"!=typeof n&&(n=!1);var r=new CustomEvent(t,{bubbles:n,detail:a});e.dispatchEvent(r)}}function k(e,t){return e?(t="boolean"==typeof t?t:!e.getAttribute("aria-pressed"),e.setAttribute("aria-pressed",t),t):void 0}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.lenth)return e[0];for(var t=Array.prototype.shift.call(e),n=e.length,a=0;n>a;a++){var r=e[a];for(var s in r)r[s]&&r[s].constructor&&r[s].constructor===Object?(t[s]=t[s]||{},x(t[s],r[s])):t[s]=r[s]}return t}}function T(){var e={supportsFullScreen:!1,isFullScreen:function(){return!1},requestFullScreen:function(){},cancelFullScreen:function(){},fullScreenEventName:"",element:null,prefix:""},n="webkit moz o ms khtml".split(" ");if("undefined"!=typeof t.cancelFullScreen)e.supportsFullScreen=!0;else for(var a=0,r=n.length;r>a;a++){if(e.prefix=n[a],"undefined"!=typeof t[e.prefix+"CancelFullScreen"]){e.supportsFullScreen=!0;break}if("undefined"!=typeof t.msExitFullscreen&&t.msFullscreenEnabled){e.prefix="ms",e.supportsFullScreen=!0;break}}return e.supportsFullScreen&&(e.fullScreenEventName="ms"==e.prefix?"MSFullscreenChange":e.prefix+"fullscreenchange",e.isFullScreen=function(e){switch("undefined"==typeof 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"undefined"==typeof 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 _(){var t={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(n){return!1}return!1}()};return t}function E(g,x){function E(t,n){x.debug&&e.console&&console[n?"warn":"log"](t)}function S(){var e=[],t=x.iconUrl+"#"+x.iconPrefix;return s(x.controls,"play-large")&&e.push('<button type="button" data-plyr="play" class="plyr__play-large">','<svg><use xlink:href="'+t+'-play" /></svg>','<span class="plyr__sr-only">'+x.i18n.play+"</span>","</button>"),e.push('<div class="plyr__controls">'),s(x.controls,"restart")&&e.push('<button type="button" data-plyr="restart">','<svg><use xlink:href="'+t+'-restart" /></svg>','<span class="plyr__sr-only">'+x.i18n.restart+"</span>","</button>"),s(x.controls,"rewind")&&e.push('<button type="button" data-plyr="rewind">','<svg><use xlink:href="'+t+'-rewind" /></svg>','<span class="plyr__sr-only">'+x.i18n.rewind+"</span>","</button>"),s(x.controls,"play")&&e.push('<button type="button" data-plyr="play">','<svg><use xlink:href="'+t+'-play" /></svg>','<span class="plyr__sr-only">'+x.i18n.play+"</span>","</button>",'<button type="button" data-plyr="pause">','<svg><use xlink:href="'+t+'-pause" /></svg>','<span class="plyr__sr-only">'+x.i18n.pause+"</span>","</button>"),s(x.controls,"fast-forward")&&e.push('<button type="button" data-plyr="fast-forward">','<svg><use xlink:href="'+t+'-fast-forward" /></svg>','<span class="plyr__sr-only">'+x.i18n.forward+"</span>","</button>"),s(x.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>% "+x.i18n.buffered,"</progress>"),x.tooltips.seek&&e.push('<span class="plyr__tooltip">00:00</span>'),e.push("</span>")),s(x.controls,"current-time")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+x.i18n.currentTime+"</span>",'<span class="plyr__time--current">00:00</span>',"</span>"),s(x.controls,"duration")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+x.i18n.duration+"</span>",'<span class="plyr__time--duration">00:00</span>',"</span>"),s(x.controls,"mute")&&e.push('<button type="button" data-plyr="mute">','<svg class="icon--muted"><use xlink:href="'+t+'-muted" /></svg>','<svg><use xlink:href="'+t+'-volume" /></svg>','<span class="plyr__sr-only">'+x.i18n.toggleMute+"</span>","</button>"),s(x.controls,"volume")&&e.push('<span class="plyr__volume">','<label for="volume{id}" class="plyr__sr-only">'+x.i18n.volume+"</label>",'<input id="volume{id}" class="plyr__volume--input" type="range" min="'+x.volumeMin+'" max="'+x.volumeMax+'" value="'+x.volume+'" data-plyr="volume">','<progress class="plyr__volume--display" max="'+x.volumeMax+'" value="'+x.volumeMin+'" role="presentation"></progress>',"</span>"),s(x.controls,"captions")&&e.push('<button type="button" data-plyr="captions">','<svg class="icon--captions-on"><use xlink:href="'+t+'-captions-on" /></svg>','<svg><use xlink:href="'+t+'-captions-off" /></svg>','<span class="plyr__sr-only">'+x.i18n.toggleCaptions+"</span>","</button>"),s(x.controls,"fullscreen")&&e.push('<button type="button" data-plyr="fullscreen">','<svg class="icon--exit-fullscreen"><use xlink:href="'+t+'-exit-fullscreen" /></svg>','<svg><use xlink:href="'+t+'-enter-fullscreen" /></svg>','<span class="plyr__sr-only">'+x.i18n.toggleFullscreen+"</span>","</button>"),e.push("</div>"),e.join("")}function I(){if(Ne.supported.full&&("audio"!=Ne.type||x.fullscreen.allowAudio)&&x.fullscreen.enabled){var e=C.supportsFullScreen;e||x.fullscreen.fallback&&!V()?(E((e?"Native":"Fallback")+" fullscreen enabled"),m(Ne.container,x.classes.fullscreen.enabled,!0)):E("Fullscreen not supported and fallback disabled"),k(Ne.buttons.fullscreen,!1),H()}}function N(){if("video"===Ne.type){L(x.selectors.captions)||Ne.videoContainer.insertAdjacentHTML("afterbegin",'<div class="'+A(x.selectors.captions)+'"></div>'),Ne.usingTextTracks=!1,Ne.media.textTracks&&(Ne.usingTextTracks=!0);for(var e,t="",n=Ne.media.childNodes,a=0;a<n.length;a++)"track"===n[a].nodeName.toLowerCase()&&(e=n[a].kind,"captions"!==e&&"subtitles"!==e||(t=n[a].getAttribute("src")));if(Ne.captionExists=!0,""===t?(Ne.captionExists=!1,E("No caption track found")):E("Caption track found; URI: "+t),Ne.captionExists){for(var r=Ne.media.textTracks,s=0;s<r.length;s++)r[s].mode="hidden";if(R(Ne),("IE"===Ne.browser.name&&Ne.browser.version>=10||"Firefox"===Ne.browser.name&&Ne.browser.version>=31)&&(E("Detected browser with known TextTrack issues - using manual fallback"),Ne.usingTextTracks=!1),Ne.usingTextTracks){E("TextTracks supported");for(var o=0;o<r.length;o++){var i=r[o];"captions"!==i.kind&&"subtitles"!==i.kind||y(i,"cuechange",function(){this.activeCues[0]&&"text"in this.activeCues[0]?M(this.activeCues[0].getCueAsHTML()):M()})}}else if(E("TextTracks not supported so rendering captions manually"),Ne.currentCaption="",Ne.captions=[],""!==t){var l=new XMLHttpRequest;l.onreadystatechange=function(){if(4===l.readyState)if(200===l.status){var e,t=[],n=l.responseText;t=n.split("\n\n");for(var a=0;a<t.length;a++){e=t[a],Ne.captions[a]=[];var r=e.split("\n"),s=0;-1===r[s].indexOf(":")&&(s=1),Ne.captions[a]=[r[s],r[s+1]]}Ne.captions.shift(),E("Successfully loaded the caption file via AJAX")}else E("There was a problem loading the caption file via AJAX",!0)},l.open("get",t,!0),l.send()}}else m(Ne.container,x.classes.captions.enabled)}}function M(e){var n=L(x.selectors.captions),a=t.createElement("span");n.innerHTML="","undefined"==typeof e&&(e=""),"string"==typeof e?a.innerHTML=e.trim():a.appendChild(e),n.appendChild(a);n.offsetHeight}function P(e){function t(e,t){var n=[];n=e.split(" --> ");for(var a=0;a<n.length;a++)n[a]=n[a].replace(/(\d+:\d+:\d+\.\d+).*/,"$1");return r(n[t])}function n(e){return t(e,0)}function a(e){return t(e,1)}function r(e){if(null===e||void 0===e)return 0;var t,n=[],a=[];return n=e.split(","),a=n[0].split(":"),t=Math.floor(60*a[0]*60)+Math.floor(60*a[1])+Math.floor(a[2])}if(!Ne.usingTextTracks&&"video"===Ne.type&&Ne.supported.full&&(Ne.subcount=0,e="number"==typeof e?e:Ne.media.currentTime,Ne.captions[Ne.subcount])){for(;a(Ne.captions[Ne.subcount][0])<e.toFixed(1);)if(Ne.subcount++,Ne.subcount>Ne.captions.length-1){Ne.subcount=Ne.captions.length-1;break}Ne.media.currentTime.toFixed(1)>=n(Ne.captions[Ne.subcount][0])&&Ne.media.currentTime.toFixed(1)<=a(Ne.captions[Ne.subcount][0])?(Ne.currentCaption=Ne.captions[Ne.subcount][1],M(Ne.currentCaption)):M()}}function R(){Ne.buttons.captions&&(m(Ne.container,x.classes.captions.enabled,!0),x.captions.defaultActive&&(m(Ne.container,x.classes.captions.active,!0),k(Ne.buttons.captions,!0)))}function B(e){return Ne.container.querySelectorAll(e)}function L(e){return B(e)[0]}function V(){try{return e.self!==e.top}catch(t){return!0}}function H(){function e(e){9===e.which&&Ne.isFullscreen&&(e.target!==a||e.shiftKey?e.target===n&&e.shiftKey&&(e.preventDefault(),a.focus()):(e.preventDefault(),n.focus()))}var t=B("input:not([disabled]), button:not([disabled])"),n=t[0],a=t[t.length-1];y(Ne.container,"keydown",e)}function O(e,t){if("string"==typeof t)d(e,Ne.media,{src:t});else if(t.constructor===Array)for(var n=t.length-1;n>=0;n--)d(e,Ne.media,t[n])}function W(){var e=x.html;E("Injecting custom controls"),e||(e=S()),e=o(e,"{seektime}",x.seekTime),e=o(e,"{id}",Math.floor(1e4*Math.random()));var n;if(null!==x.selectors.controls.container&&(n=x.selectors.controls.container,"string"==typeof selector&&(n=t.querySelector(n))),n instanceof HTMLElement||(n=Ne.container),n.insertAdjacentHTML("beforeend",e),x.tooltips.controls)for(var a=B([x.selectors.controls.wrapper," ",x.selectors.labels," .",x.classes.hidden].join("")),r=a.length-1;r>=0;r--){var s=a[r];m(s,x.classes.hidden,!1),m(s,x.classes.tooltip,!0)}}function G(){try{return Ne.controls=L(x.selectors.controls.wrapper),Ne.buttons={},Ne.buttons.seek=L(x.selectors.buttons.seek),Ne.buttons.play=B(x.selectors.buttons.play),Ne.buttons.pause=L(x.selectors.buttons.pause),Ne.buttons.restart=L(x.selectors.buttons.restart),Ne.buttons.rewind=L(x.selectors.buttons.rewind),Ne.buttons.forward=L(x.selectors.buttons.forward),Ne.buttons.fullscreen=L(x.selectors.buttons.fullscreen),Ne.buttons.mute=L(x.selectors.buttons.mute),Ne.buttons.captions=L(x.selectors.buttons.captions),Ne.progress={},Ne.progress.container=L(x.selectors.progress.container),Ne.progress.buffer={},Ne.progress.buffer.bar=L(x.selectors.progress.buffer),Ne.progress.buffer.text=Ne.progress.buffer.bar&&Ne.progress.buffer.bar.getElementsByTagName("span")[0],Ne.progress.played=L(x.selectors.progress.played),Ne.progress.tooltip=Ne.progress.container&&Ne.progress.container.querySelector("."+x.classes.tooltip),Ne.volume={},Ne.volume.input=L(x.selectors.volume.input),Ne.volume.display=L(x.selectors.volume.display),Ne.duration=L(x.selectors.duration),Ne.currentTime=L(x.selectors.currentTime),Ne.seekTime=B(x.selectors.seekTime),!0}catch(e){return E("It looks like there is a problem with your controls html",!0),q(!0),!1}}function Y(){m(Ne.container,x.selectors.container.replace(".",""),Ne.supported.full)}function q(e){e?Ne.media.setAttribute("controls",""):Ne.media.removeAttribute("controls")}function z(e){var t=x.i18n.play;if("undefined"!=typeof x.title&&x.title.length&&(t+=", "+x.title),Ne.supported.full&&Ne.buttons.play)for(var n=Ne.buttons.play.length-1;n>=0;n--)Ne.buttons.play[n].setAttribute("aria-label",t);e instanceof HTMLElement&&e.setAttribute("title",x.i18n.frameTitle.replace("{title}",x.title))}function Q(){if(!Ne.media)return E("No audio or video element found",!0),!1;if(Ne.supported.full&&(m(Ne.container,x.classes.type.replace("{0}",Ne.type),!0),s(x.types.embed,Ne.type)&&m(Ne.container,x.classes.type.replace("{0}","video"),!0),m(Ne.container,x.classes.stopped,x.autoplay),m(Ne.container,x.classes.isIos,Ne.browser.ios),m(Ne.container,x.classes.isTouch,Ne.browser.touch),"video"===Ne.type)){var e=t.createElement("div");e.setAttribute("class",x.classes.videoWrapper),i(Ne.media,e),Ne.videoContainer=e}s(x.types.embed,Ne.type)&&(j(),Ne.embedId=null)}function j(){for(var n=t.createElement("div"),a=Ne.embedId,s=Ne.type+"-"+Math.floor(1e4*Math.random()),o=B('[id^="'+Ne.type+'-"]'),i=o.length-1;i>=0;i--)u(o[i]);if(m(Ne.media,x.classes.videoWrapper,!0),m(Ne.media,x.classes.embedWrapper,!0),"youtube"===Ne.type)Ne.media.appendChild(n),n.setAttribute("id",s),"object"==typeof YT?D(a,n):(r(x.urls.youtube.api),e.onYouTubeReadyCallbacks=e.onYouTubeReadyCallbacks||[],e.onYouTubeReadyCallbacks.push(function(){D(a,n)}),e.onYouTubeIframeAPIReady=function(){e.onYouTubeReadyCallbacks.forEach(function(e){e()})});else if("vimeo"===Ne.type){var l=t.createElement("iframe");l.loaded=!1,y(l,"load",function(){l.loaded=!0}),p(l,{src:"https://player.vimeo.com/video/"+a+"?player_id="+s+"&api=1&badge=0&byline=0&portrait=0&title=0",id:s,webkitallowfullscreen:"",mozallowfullscreen:"",allowfullscreen:"",frameborder:0}),Ne.supported.full?(n.appendChild(l),Ne.media.appendChild(n)):Ne.media.appendChild(l),"$f"in e||r(x.urls.vimeo.api);var c=e.setInterval(function(){"$f"in e&&l.loaded&&(e.clearInterval(c),U.call(l))},50)}else if("soundcloud"===Ne.type){var d=t.createElement("iframe");d.loaded=!1,y(d,"load",function(){d.loaded=!0}),p(d,{src:"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/"+a,id:s}),n.appendChild(d),Ne.media.appendChild(n),e.SC||r(x.urls.soundcloud.api);var A=e.setInterval(function(){e.SC&&d.loaded&&(e.clearInterval(A),Z.call(d))},50)}}function X(){Ne.container.plyr.embed=Ne.embed,Ie(),z(L("iframe"))}function D(t,n){"timer"in Ne||(Ne.timer={}),Ne.embed=new YT.Player(n.id,{videoId:t,playerVars:{autoplay:x.autoplay?1:0,controls:Ne.supported.full?0:1,rel:0,showinfo:0,iv_load_policy:3,cc_load_policy:x.captions.defaultActive?1:0,cc_lang_pref:"en",wmode:"transparent",modestbranding:1,disablekb:1,origin:"*"},events:{onError:function(e){h(Ne.container,"error",!0,{code:e.data,embed:e.target})},onReady:function(t){var n=t.target;Ne.media.play=function(){n.playVideo(),Ne.media.paused=!1},Ne.media.pause=function(){n.pauseVideo(),Ne.media.paused=!0},Ne.media.stop=function(){n.stopVideo(),Ne.media.paused=!0},Ne.media.duration=n.getDuration(),Ne.media.paused=!0,Ne.media.currentTime=n.getCurrentTime(),Ne.media.muted=n.isMuted(),x.title=n.getVideoData().title,h(Ne.media,"timeupdate"),e.clearInterval(Ne.timer.buffering),Ne.timer.buffering=e.setInterval(function(){Ne.media.buffered=n.getVideoLoadedFraction(),h(Ne.media,"progress"),1===Ne.media.buffered&&(e.clearInterval(Ne.timer.buffering),h(Ne.media,"canplaythrough"))},200),X(),be()},onStateChange:function(t){var n=t.target;switch(e.clearInterval(Ne.timer.playing),t.data){case 0:Ne.media.paused=!0,h(Ne.media,"ended");break;case 1:Ne.media.paused=!1,Ne.media.seeking=!1,h(Ne.media,"play"),h(Ne.media,"playing"),Ne.timer.playing=e.setInterval(function(){Ne.media.currentTime=n.getCurrentTime(),h(Ne.media,"timeupdate")},100);break;case 2:Ne.media.paused=!0,h(Ne.media,"pause")}h(Ne.container,"statechange",!1,{code:t.data})}}})}function U(){Ne.embed=$f(this),Ne.embed.addEvent("ready",function(){Ne.media.play=function(){Ne.embed.api("play"),Ne.media.paused=!1},Ne.media.pause=function(){Ne.embed.api("pause"),Ne.media.paused=!0},Ne.media.stop=function(){Ne.embed.api("stop"),Ne.media.paused=!0},Ne.media.paused=!0,Ne.media.currentTime=0,X(),Ne.embed.api("getCurrentTime",function(e){Ne.media.currentTime=e,h(Ne.media,"timeupdate")}),Ne.embed.api("getDuration",function(e){Ne.media.duration=e,be()}),Ne.embed.addEvent("play",function(){Ne.media.paused=!1,h(Ne.media,"play"),h(Ne.media,"playing")}),Ne.embed.addEvent("pause",function(){Ne.media.paused=!0,h(Ne.media,"pause")}),Ne.embed.addEvent("playProgress",function(e){Ne.media.seeking=!1,Ne.media.currentTime=e.seconds,h(Ne.media,"timeupdate")}),Ne.embed.addEvent("loadProgress",function(e){Ne.media.buffered=e.percent,h(Ne.media,"progress"),1===parseInt(e.percent)&&h(Ne.media,"canplaythrough")}),Ne.embed.addEvent("finish",function(){Ne.media.paused=!0,h(Ne.media,"ended")}),x.autoplay&&Ne.embed.api("play")})}function Z(){Ne.embed=e.SC.Widget(this),Ne.embed.bind(e.SC.Widget.Events.READY,function(){Ne.media.play=function(){Ne.embed.play(),Ne.media.paused=!1},Ne.media.pause=function(){Ne.embed.pause(),Ne.media.paused=!0},Ne.media.stop=function(){Ne.embed.seekTo(0),Ne.embed.pause(),Ne.media.paused=!0},Ne.media.paused=!0,Ne.media.currentTime=0,X(),Ne.embed.getPosition(function(e){Ne.media.currentTime=e,h(Ne.media,"timeupdate")}),Ne.embed.getDuration(function(e){Ne.media.duration=e/1e3,be()}),Ne.embed.bind(e.SC.Widget.Events.PLAY,function(){Ne.media.paused=!1,h(Ne.media,"play"),h(Ne.media,"playing")}),Ne.embed.bind(e.SC.Widget.Events.PAUSE,function(){Ne.media.paused=!0,h(Ne.media,"pause")}),Ne.embed.bind(e.SC.Widget.Events.PLAY_PROGRESS,function(e){Ne.media.seeking=!1,Ne.media.currentTime=e.currentPosition/1e3,h(Ne.media,"timeupdate")}),Ne.embed.bind(e.SC.Widget.Events.LOAD_PROGRESS,function(e){Ne.media.buffered=e.loadProgress,h(Ne.media,"progress"),1===parseInt(e.loadProgress)&&h(Ne.media,"canplaythrough")}),Ne.embed.bind(e.SC.Widget.Events.FINISH,function(){Ne.media.paused=!0,h(Ne.media,"ended")}),x.autoplay&&Ne.embed.play()})}function $(){"play"in Ne.media&&Ne.media.play()}function J(){"pause"in Ne.media&&Ne.media.pause()}function K(e){e===!0?$():e===!1?J():Ne.media[Ne.media.paused?"play":"pause"]()}function ee(e){"number"!=typeof e&&(e=x.seekTime),ne(Ne.media.currentTime-e)}function te(e){"number"!=typeof e&&(e=x.seekTime),ne(Ne.media.currentTime+e)}function ne(e){var t=0,n=Ne.media.paused,a=ae();"number"==typeof e?t=e:"object"!=typeof e||"input"!==e.type&&"change"!==e.type||(t=e.target.value/e.target.max*a),0>t?t=0:t>a&&(t=a),ge(t);try{Ne.media.currentTime=t.toFixed(1)}catch(r){}if(s(x.types.embed,Ne.type)){switch(Ne.type){case"youtube":Ne.embed.seekTo(t);break;case"vimeo":Ne.embed.api("seekTo",t.toFixed(0));break;case"soundcloud":Ne.embed.seekTo(1e3*t)}n&&J(),h(Ne.media,"timeupdate"),Ne.media.seeking=!0}E("Seeking to "+Ne.media.currentTime+" seconds"),P(t)}function ae(){var e=parseInt(x.duration),t=0;return null===Ne.media.duration||isNaN(Ne.media.duration)||(t=Ne.media.duration),isNaN(e)?t:e}function re(){m(Ne.container,x.classes.playing,!Ne.media.paused),m(Ne.container,x.classes.stopped,Ne.media.paused),ke(Ne.media.paused)}function se(e){var n=C.supportsFullScreen;e&&e.type===C.fullScreenEventName?Ne.isFullscreen=C.isFullScreen(Ne.container):n?(C.isFullScreen(Ne.container)?C.cancelFullScreen():C.requestFullScreen(Ne.container),Ne.isFullscreen=C.isFullScreen(Ne.container)):(Ne.isFullscreen=!Ne.isFullscreen,Ne.isFullscreen?(y(t,"keyup",oe),t.body.style.overflow="hidden"):(b(t,"keyup",oe),t.body.style.overflow="")),m(Ne.container,x.classes.fullscreen.active,Ne.isFullscreen),Ne.isFullscreen?Ne.container.setAttribute("tabindex","-1"):Ne.container.removeAttribute("tabindex"),H(Ne.isFullscreen),k(Ne.buttons.fullscreen,Ne.isFullscreen),h(Ne.container,Ne.isFullscreen?"enterfullscreen":"exitfullscreen")}function oe(e){27===(e.which||e.charCode||e.keyCode)&&Ne.isFullscreen&&se()}function ie(e){if("boolean"!=typeof e&&(e=!Ne.media.muted),k(Ne.buttons.mute,e),Ne.media.muted=e,0===Ne.media.volume&&le(x.volume),s(x.types.embed,Ne.type)){switch(Ne.type){case"youtube":Ne.embed[Ne.media.muted?"mute":"unMute"]();break;case"vimeo":Ne.embed.api("setVolume",Ne.media.muted?0:parseFloat(x.volume/x.volumeMax));break;case"soundcloud":Ne.embed.setVolume(Ne.media.muted?0:parseFloat(x.volume/x.volumeMax))}h(Ne.media,"volumechange")}}function le(t){var n=x.volumeMax,a=x.volumeMin;if("undefined"==typeof t&&(t=x.volume,x.storage.enabled&&_().supported&&(t=e.localStorage.getItem(x.storage.key),e.localStorage.removeItem("plyr-volume"))),(null===t||isNaN(t))&&(t=x.volume),t>n&&(t=n),a>t&&(t=a),Ne.media.volume=parseFloat(t/n),Ne.volume.display&&(Ne.volume.display.value=t),s(x.types.embed,Ne.type)){switch(Ne.type){case"youtube":Ne.embed.setVolume(100*Ne.media.volume);break;case"vimeo":Ne.embed.api("setVolume",Ne.media.volume);break;case"soundcloud":Ne.embed.setVolume(Ne.media.volume)}h(Ne.media,"volumechange")}Ne.media.muted&&t>0&&ie()}function ue(){var e=Ne.media.muted?0:Ne.media.volume*x.volumeMax;le(e+x.volumeStep/5)}function ce(){var e=Ne.media.muted?0:Ne.media.volume*x.volumeMax;le(e-x.volumeStep/5)}function pe(){var t=Ne.media.muted?0:Ne.media.volume*x.volumeMax;Ne.supported.full&&(Ne.volume.input&&(Ne.volume.input.value=t),Ne.volume.display&&(Ne.volume.display.value=t)),x.storage.enabled&&_().supported&&!isNaN(t)&&e.localStorage.setItem(x.storage.key,t),m(Ne.container,x.classes.muted,0===t),Ne.supported.full&&Ne.buttons.mute&&k(Ne.buttons.mute,0===t)}function de(e){Ne.supported.full&&Ne.buttons.captions&&("boolean"!=typeof e&&(e=-1===Ne.container.className.indexOf(x.classes.captions.active)),Ne.captionsEnabled=e,k(Ne.buttons.captions,Ne.captionsEnabled),m(Ne.container,x.classes.captions.active,Ne.captionsEnabled),h(Ne.container,Ne.captionsEnabled?"captionsenabled":"captionsdisabled"))}function Ae(e){var t="waiting"===e.type;clearTimeout(Ne.timers.loading),Ne.timers.loading=setTimeout(function(){m(Ne.container,x.classes.loading,t)},t?250:0)}function me(e){if(Ne.supported.full){var t=Ne.progress.played,n=0,a=ae();if(e)switch(e.type){case"timeupdate":case"seeking":n=w(Ne.media.currentTime,a),"timeupdate"==e.type&&Ne.buttons.seek&&(Ne.buttons.seek.value=n);break;case"playing":case"progress":t=Ne.progress.buffer,n=function(){var e=Ne.media.buffered;return e&&e.length?w(e.end(0),a):"number"==typeof e?100*e:0}()}fe(t,n)}}function fe(e,t){if(Ne.supported.full){if("undefined"==typeof t&&(t=0),"undefined"==typeof e){if(!Ne.progress||!Ne.progress.buffer)return;e=Ne.progress.buffer}e instanceof HTMLElement?e.value=t:e&&(e.bar&&(e.bar.value=t),e.text&&(e.text.innerHTML=t))}}function ye(e,t){if(t){isNaN(e)&&(e=0),Ne.secs=parseInt(e%60),Ne.mins=parseInt(e/60%60),Ne.hours=parseInt(e/60/60%60);var n=parseInt(ae()/60/60%60)>0;Ne.secs=("0"+Ne.secs).slice(-2),Ne.mins=("0"+Ne.mins).slice(-2),t.innerHTML=(n?Ne.hours+":":"")+Ne.mins+":"+Ne.secs}}function be(){if(Ne.supported.full){var e=ae()||0;!Ne.duration&&x.displayDuration&&Ne.media.paused&&ye(e,Ne.currentTime),Ne.duration&&ye(e,Ne.duration),he()}}function ve(e){ye(Ne.media.currentTime,Ne.currentTime),e&&"timeupdate"==e.type&&Ne.media.seeking||me(e)}function ge(e){"number"!=typeof e&&(e=0);var t=ae(),n=w(e,t);Ne.progress&&Ne.progress.played&&(Ne.progress.played.value=n),Ne.buttons&&Ne.buttons.seek&&(Ne.buttons.seek.value=n)}function he(e){var t=ae();if(x.tooltips.seek&&Ne.progress.container&&0!==t){var n=Ne.progress.container.getBoundingClientRect(),a=0,r=x.classes.tooltip+"--visible";if(e)a=100/n.width*(e.pageX-n.left);else{if(!f(Ne.progress.tooltip,r))return;a=Ne.progress.tooltip.style.left.replace("%","")}0>a?a=0:a>100&&(a=100),ye(t/100*a,Ne.progress.tooltip),Ne.progress.tooltip.style.left=a+"%",e&&s(["mouseenter","mouseleave"],e.type)&&m(Ne.progress.tooltip,r,"mouseenter"===e.type)}}function ke(t){if(x.hideControls&&"audio"!==Ne.type){var n=0,a=!1,r=t;"boolean"!=typeof t&&(t&&t.type?(a="enterfullscreen"===t.type,r=s(["mousemove","mouseenter","focus"],t.type),"mousemove"===t.type&&(n=2e3),"focus"===t.type&&(n=3e3)):r=!1),e.clearTimeout(Ne.timers.hover),(r||Ne.media.paused)&&(m(Ne.container,x.classes.hideControls,!1),Ne.media.paused)||r&&Ne.media.paused||(Ne.timers.hover=e.setTimeout(function(){Ne.controls.active&&!a||m(Ne.container,x.classes.hideControls,!0)},n))}}function we(e){if("undefined"!=typeof e)return void xe(e);var t;switch(Ne.type){case"youtube":t=Ne.embed.getVideoUrl();break;case"vimeo":Ne.embed.api("getVideoUrl",function(e){t=e});break;case"soundcloud":Ne.embed.getCurrentSound(function(e){t=e.permalink_url});break;default:t=Ne.media.currentSrc}return t||""}function xe(n){if(!("undefined"!=typeof n&&"sources"in n&&n.sources.length))return void E("Invalid source format",!0);if(J(),ge(),fe(),Ce(),"youtube"===Ne.type?(Ne.embed.destroy(),e.clearInterval(Ne.timer.buffering),e.clearInterval(Ne.timer.playing)):"video"===Ne.type&&Ne.videoContainer&&u(Ne.videoContainer),Ne.embed=null,u(Ne.media),"type"in n&&(Ne.type=n.type,"video"===Ne.type)){var a=n.sources[0];"type"in a&&s(x.types.embed,a.type)&&(Ne.type=a.type)}switch(Ne.supported=F.supported(Ne.type),Ne.type){case"video":Ne.media=t.createElement("video");break;case"audio":Ne.media=t.createElement("audio");break;case"youtube":case"vimeo":case"soundcloud":Ne.media=t.createElement("div"),Ne.embedId=n.sources[0].src}c(Ne.container,Ne.media),"undefined"!=typeof n.autoplay&&(x.autoplay=n.autoplay),s(x.types.html5,Ne.type)&&(x.crossorigin&&Ne.media.setAttribute("crossorigin",""),x.autoplay&&Ne.media.setAttribute("autoplay",""),"poster"in n&&Ne.media.setAttribute("poster",n.poster),x.loop&&Ne.media.setAttribute("loop","")),Ne.container.className=Ne.originalClassName,m(Ne.container,x.classes.fullscreen.active,Ne.isFullscreen),m(Ne.container,x.classes.captions.active,Ne.captionsEnabled),Y(),s(x.types.html5,Ne.type)&&O("source",n.sources),Q(),s(x.types.html5,Ne.type)&&("tracks"in n&&O("track",n.tracks),Ne.media.load(),Ie(),be()),x.title=n.title,z(),Ne.container.plyr.media=Ne.media}function Te(e){"video"===Ne.type&&Ne.media.setAttribute("poster",e)}function _e(){function n(){var e=Ne.media.paused;e?$():J();var t=Ne.buttons[e?"play":"pause"],n=Ne.buttons[e?"pause":"play"];if(n=n&&n.length>1?n[n.length-1]:n[0]){var a=f(t,x.classes.tabFocus);setTimeout(function(){n.focus(),a&&(m(t,x.classes.tabFocus,!1),m(n,x.classes.tabFocus,!0))},100)}}function a(){var e=t.activeElement;e&&e!=t.body?t.querySelector&&(e=t.querySelector(":focus")):e=null;for(var n in Ne.buttons){var a=Ne.buttons[n];if(a instanceof NodeList)for(var r=0;r<a.length;r++)m(a[r],x.classes.tabFocus,a[r]===e);else m(a,x.classes.tabFocus,a===e)}}var r="IE"==Ne.browser.name?"change":"input";y(e,"keyup",function(e){var t=e.keyCode?e.keyCode:e.which;9==t&&a()}),y(t.body,"click",function(){m(L("."+x.classes.tabFocus),x.classes.tabFocus,!1)});for(var s in Ne.buttons){var o=Ne.buttons[s];y(o,"blur",function(){m(o,"tab-focus",!1)})}v(Ne.buttons.play,"click",x.listeners.play,n),v(Ne.buttons.pause,"click",x.listeners.pause,n),v(Ne.buttons.restart,"click",x.listeners.restart,ne),v(Ne.buttons.rewind,"click",x.listeners.rewind,ee),v(Ne.buttons.forward,"click",x.listeners.forward,te),v(Ne.buttons.seek,r,x.listeners.seek,ne),v(Ne.volume.input,r,x.listeners.volume,function(){le(Ne.volume.input.value)}),v(Ne.buttons.mute,"click",x.listeners.mute,ie),v(Ne.buttons.fullscreen,"click",x.listeners.fullscreen,se),C.supportsFullScreen&&y(t,C.fullScreenEventName,se),y(Ne.buttons.captions,"click",de),y(Ne.progress.container,"mouseenter mouseleave mousemove",he),x.hideControls&&(y(Ne.container,"mouseenter mouseleave mousemove enterfullscreen",ke),y(Ne.controls,"mouseenter mouseleave",function(e){Ne.controls.active="mouseenter"===e.type}),y(Ne.controls,"focus blur",ke,!0)),y(Ne.volume.input,"wheel",function(e){e.preventDefault(),(e.deltaY<0||e.deltaX>0)&&ce(),(e.deltaY>0||e.deltaX<0)&&ue()})}function Ee(){if(y(Ne.media,"timeupdate seeking",ve),y(Ne.media,"timeupdate",P),y(Ne.media,"durationchange loadedmetadata",be),y(Ne.media,"ended",function(){"video"===Ne.type&&M(),re(),ne(0),be(),"video"===Ne.type&&x.showPosterOnEnd&&Ne.media.load()}),y(Ne.media,"progress playing",me),y(Ne.media,"volumechange",pe),y(Ne.media,"play pause",re),y(Ne.media,"waiting canplay seeked",Ae),x.clickToPlay&&"audio"!==Ne.type){var e=L("."+x.classes.videoWrapper);if(!e)return;e.style.cursor="pointer",y(e,"click",function(){Ne.media.paused?$():Ne.media.ended?(ne(),$()):J()})}y(Ne.media,x.events.join(" "),function(e){h(Ne.container,e.type,!0)})}function Ce(){if(s(x.types.html5,Ne.type)){for(var e=Ne.media.querySelectorAll("source"),t=0;t<e.length;t++)u(e[t]);Ne.media.setAttribute("src","data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAGm1kYXQAAAGzABAHAAABthBgUYI9t+8AAAMNbW9vdgAAAGxtdmhkAAAAAMXMvvrFzL76AAAD6AAAACoAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAABhpb2RzAAAAABCAgIAHAE/////+/wAAAiF0cmFrAAAAXHRraGQAAAAPxcy++sXMvvoAAAABAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAgAAAAIAAAAAAG9bWRpYQAAACBtZGhkAAAAAMXMvvrFzL76AAAAGAAAAAEVxwAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABaG1pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAShzdGJsAAAAxHN0c2QAAAAAAAAAAQAAALRtcDR2AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAgACABIAAAASAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAXmVzZHMAAAAAA4CAgE0AAQAEgICAPyARAAAAAAMNQAAAAAAFgICALQAAAbABAAABtYkTAAABAAAAASAAxI2IAMUARAEUQwAAAbJMYXZjNTMuMzUuMAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAAABAAAAAQAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAASAAAAAQAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYHVkdGEAAABYbWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAraWxzdAAAACOpdG9vAAAAG2RhdGEAAAABAAAAAExhdmY1My4yMS4x"),
2
- Ne.media.load(),E("Cancelled network requests for old media")}}function Fe(){if(!Ne.init)return null;if(Ne.container.setAttribute("class",A(x.selectors.container)),Ne.init=!1,u(L(x.selectors.controls.wrapper)),"youtube"===Ne.type)return void Ne.embed.destroy();"video"===Ne.type&&(u(L(x.selectors.captions)),l(Ne.videoContainer)),q(!0);var e=Ne.media.cloneNode(!0);Ne.media.parentNode.replaceChild(e,Ne.media)}function Se(){if(Ne.init)return null;if(C=T(),Ne.browser=n(),Ne.media=Ne.container.querySelectorAll("audio, video")[0],Ne.media||(Ne.media=Ne.container.querySelectorAll("div")[0]),Ne.media){Ne.originalClassName=Ne.container.className;var e=Ne.media.tagName.toLowerCase();if("div"===e?(Ne.type=Ne.media.getAttribute("data-type"),Ne.embedId=Ne.media.getAttribute("data-video-id"),Ne.media.removeAttribute("data-type"),Ne.media.removeAttribute("data-video-id")):(Ne.type=e,x.crossorigin=null!==Ne.media.getAttribute("crossorigin"),x.autoplay=x.autoplay||null!==Ne.media.getAttribute("autoplay"),x.loop=x.loop||null!==Ne.media.getAttribute("loop")),Ne.supported=F.supported(Ne.type),Y(),!Ne.supported.basic)return!1;if(E(Ne.browser.name+" "+Ne.browser.version),Q(),s(x.types.html5,Ne.type)){if(!Ne.supported.full)return void(Ne.init=!0);Ie(),z(),x.autoplay&&$()}Ne.init=!0}}function Ie(){if(!Ne.supported.full)return E("No full support for this media type ("+Ne.type+")",!0),u(L(x.selectors.controls.wrapper)),u(L(x.selectors.buttons.play)),void q(!0);var e=!B(x.selectors.controls.wrapper).length;e&&W(),G()&&(e&&_e(),Ee(),q(),I(),N(),le(),pe(),ve(),re(),be(),h(Ne.container,"ready"))}var Ne=this;return Ne.container=g,Ne.timers={},E(x),Se(),Ne.init?{media:Ne.media,play:$,pause:J,restart:ne,rewind:ee,forward:te,seek:ne,source:we,poster:Te,setVolume:le,togglePlay:K,toggleMute:ie,toggleCaptions:de,toggleFullscreen:se,isFullscreen:function(){return Ne.isFullscreen||!1},support:function(e){return a(Ne,e)},destroy:Fe,restore:Se}:{}}var C,F={},S={enabled:!0,debug:!1,autoplay:!1,loop:!1,seekTime:10,volume:5,volumeMin:0,volumeMax:10,volumeStep:1,duration:null,displayDuration:!0,iconPrefix:"plyr",iconUrl:"",clickToPlay:!0,hideControls:!0,showPosterOnEnd:!1,tooltips:{controls:!1,seek:!0},selectors:{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:{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://cdn.plyr.io/froogaloop/1.0.1/plyr.froogaloop.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:["ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","emptied"]};return F.supported=function(e){var a,r,s=n(),o="IE"===s.name&&s.version<=9,i=/iPhone|iPod/i.test(navigator.userAgent),l=!!t.createElement("audio").canPlayType,u=!!t.createElement("video").canPlayType;switch(e){case"video":a=u,r=a&&!o&&!i;break;case"audio":a=l,r=a&&!o;break;case"vimeo":case"youtube":case"soundcloud":a=!0,r=!o&&!i;break;default:a=l&&u,r=a&&!o}return{basic:a,full:r}},F.setup=function(e,n){var a=[];if("string"==typeof e?e=t.querySelectorAll(e):e instanceof HTMLElement?e=[e]:e instanceof NodeList||"string"==typeof e||("undefined"==typeof n&&"object"==typeof e&&(n=e),e=t.querySelectorAll(S.selectors.container)),!F.supported().basic||!e.length)return!1;for(var r=0;r<e.length;r++){var s=e[r];if("undefined"==typeof s.plyr){var o=x(S,n,JSON.parse(s.getAttribute("data-plyr")));if(!o.enabled)return;var i=new E(s,o);s.plyr=Object.keys(i).length?i:!1,h(s,"setup",{plyr:s.plyr})}a.push(s.plyr)}return a},F}),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}return"function"==typeof window.CustomEvent?!1:(e.prototype=window.Event.prototype,void(window.CustomEvent=e))}();
1
+ // ==========================================================================
2
+ // Plyr
3
+ // plyr.js v2.0.11
4
+ // https://github.com/selz/plyr
5
+ // License: The MIT License (MIT)
6
+ // ==========================================================================
7
+ // Credits: http://paypal.github.io/accessible-html5-video-player/
8
+ // ==========================================================================
9
+
10
+ ;(function(root, factory) {
11
+ 'use strict';
12
+ /*global define,module*/
13
+
14
+ if (typeof module === 'object' && typeof module.exports === 'object') {
15
+ // Node, CommonJS-like
16
+ module.exports = factory(root, document);
17
+ } else if (typeof define === 'function' && define.amd) {
18
+ // AMD
19
+ define([], function () { return factory(root, document); });
20
+ } else {
21
+ // Browser globals (root is window)
22
+ root.plyr = factory(root, document);
23
+ }
24
+ }(typeof window !== 'undefined' ? window : this, function(window, document) {
25
+ 'use strict';
26
+
27
+ // Globals
28
+ var fullscreen,
29
+ scroll = { x: 0, y: 0 },
30
+
31
+ // Default config
32
+ defaults = {
33
+ enabled: true,
34
+ debug: false,
35
+ autoplay: false,
36
+ loop: false,
37
+ seekTime: 10,
38
+ volume: 10,
39
+ volumeMin: 0,
40
+ volumeMax: 10,
41
+ volumeStep: 1,
42
+ duration: null,
43
+ displayDuration: true,
44
+ loadSprite: true,
45
+ iconPrefix: 'plyr',
46
+ iconUrl: 'https://cdn.plyr.io/2.0.11/plyr.svg',
47
+ clickToPlay: true,
48
+ hideControls: true,
49
+ showPosterOnEnd: false,
50
+ disableContextMenu: true,
51
+ keyboardShorcuts: {
52
+ focused: true,
53
+ global: false
54
+ },
55
+ tooltips: {
56
+ controls: false,
57
+ seek: true
58
+ },
59
+ selectors: {
60
+ html5: 'video, audio',
61
+ embed: '[data-type]',
62
+ editable: 'input, textarea, select, [contenteditable]',
63
+ container: '.plyr',
64
+ controls: {
65
+ container: null,
66
+ wrapper: '.plyr__controls'
67
+ },
68
+ labels: '[data-plyr]',
69
+ buttons: {
70
+ seek: '[data-plyr="seek"]',
71
+ play: '[data-plyr="play"]',
72
+ pause: '[data-plyr="pause"]',
73
+ restart: '[data-plyr="restart"]',
74
+ rewind: '[data-plyr="rewind"]',
75
+ forward: '[data-plyr="fast-forward"]',
76
+ mute: '[data-plyr="mute"]',
77
+ captions: '[data-plyr="captions"]',
78
+ fullscreen: '[data-plyr="fullscreen"]'
79
+ },
80
+ volume: {
81
+ input: '[data-plyr="volume"]',
82
+ display: '.plyr__volume--display'
83
+ },
84
+ progress: {
85
+ container: '.plyr__progress',
86
+ buffer: '.plyr__progress--buffer',
87
+ played: '.plyr__progress--played'
88
+ },
89
+ captions: '.plyr__captions',
90
+ currentTime: '.plyr__time--current',
91
+ duration: '.plyr__time--duration'
92
+ },
93
+ classes: {
94
+ setup: 'plyr--setup',
95
+ ready: 'plyr--ready',
96
+ videoWrapper: 'plyr__video-wrapper',
97
+ embedWrapper: 'plyr__video-embed',
98
+ type: 'plyr--{0}',
99
+ stopped: 'plyr--stopped',
100
+ playing: 'plyr--playing',
101
+ muted: 'plyr--muted',
102
+ loading: 'plyr--loading',
103
+ hover: 'plyr--hover',
104
+ tooltip: 'plyr__tooltip',
105
+ hidden: 'plyr__sr-only',
106
+ hideControls: 'plyr--hide-controls',
107
+ isIos: 'plyr--is-ios',
108
+ isTouch: 'plyr--is-touch',
109
+ captions: {
110
+ enabled: 'plyr--captions-enabled',
111
+ active: 'plyr--captions-active'
112
+ },
113
+ fullscreen: {
114
+ enabled: 'plyr--fullscreen-enabled',
115
+ active: 'plyr--fullscreen-active'
116
+ },
117
+ tabFocus: 'tab-focus'
118
+ },
119
+ captions: {
120
+ defaultActive: false
121
+ },
122
+ fullscreen: {
123
+ enabled: true,
124
+ fallback: true,
125
+ allowAudio: false
126
+ },
127
+ storage: {
128
+ enabled: true,
129
+ key: 'plyr'
130
+ },
131
+ controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'],
132
+ i18n: {
133
+ restart: 'Restart',
134
+ rewind: 'Rewind {seektime} secs',
135
+ play: 'Play',
136
+ pause: 'Pause',
137
+ forward: 'Forward {seektime} secs',
138
+ played: 'played',
139
+ buffered: 'buffered',
140
+ currentTime: 'Current time',
141
+ duration: 'Duration',
142
+ volume: 'Volume',
143
+ toggleMute: 'Toggle Mute',
144
+ toggleCaptions: 'Toggle Captions',
145
+ toggleFullscreen: 'Toggle Fullscreen',
146
+ frameTitle: 'Player for {title}'
147
+ },
148
+ types: {
149
+ embed: ['youtube', 'vimeo', 'soundcloud'],
150
+ html5: ['video', 'audio']
151
+ },
152
+ // URLs
153
+ urls: {
154
+ vimeo: {
155
+ api: 'https://player.vimeo.com/api/player.js',
156
+ },
157
+ youtube: {
158
+ api: 'https://www.youtube.com/iframe_api'
159
+ },
160
+ soundcloud: {
161
+ api: 'https://w.soundcloud.com/player/api.js'
162
+ }
163
+ },
164
+ // Custom control listeners
165
+ listeners: {
166
+ seek: null,
167
+ play: null,
168
+ pause: null,
169
+ restart: null,
170
+ rewind: null,
171
+ forward: null,
172
+ mute: null,
173
+ volume: null,
174
+ captions: null,
175
+ fullscreen: null
176
+ },
177
+ // Events to watch on HTML5 media elements
178
+ events: ['ready', 'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied'],
179
+ // Logging
180
+ logPrefix: '[Plyr]'
181
+ };
182
+
183
+ // Credits: http://paypal.github.io/accessible-html5-video-player/
184
+ // Unfortunately, due to mixed support, UA sniffing is required
185
+ function _browserSniff() {
186
+ var ua = navigator.userAgent,
187
+ name = navigator.appName,
188
+ fullVersion = '' + parseFloat(navigator.appVersion),
189
+ majorVersion = parseInt(navigator.appVersion, 10),
190
+ nameOffset,
191
+ verOffset,
192
+ ix,
193
+ isIE = false,
194
+ isFirefox = false,
195
+ isChrome = false,
196
+ isSafari = false;
197
+
198
+ if ((navigator.appVersion.indexOf('Windows NT') !== -1) && (navigator.appVersion.indexOf('rv:11') !== -1)) {
199
+ // MSIE 11
200
+ isIE = true;
201
+ name = 'IE';
202
+ fullVersion = '11';
203
+ } else if ((verOffset = ua.indexOf('MSIE')) !== -1) {
204
+ // MSIE
205
+ isIE = true;
206
+ name = 'IE';
207
+ fullVersion = ua.substring(verOffset + 5);
208
+ } else if ((verOffset = ua.indexOf('Chrome')) !== -1) {
209
+ // Chrome
210
+ isChrome = true;
211
+ name = 'Chrome';
212
+ fullVersion = ua.substring(verOffset + 7);
213
+ } else if ((verOffset = ua.indexOf('Safari')) !== -1) {
214
+ // Safari
215
+ isSafari = true;
216
+ name = 'Safari';
217
+ fullVersion = ua.substring(verOffset + 7);
218
+ if ((verOffset = ua.indexOf('Version')) !== -1) {
219
+ fullVersion = ua.substring(verOffset + 8);
220
+ }
221
+ } else if ((verOffset = ua.indexOf('Firefox')) !== -1) {
222
+ // Firefox
223
+ isFirefox = true;
224
+ name = 'Firefox';
225
+ fullVersion = ua.substring(verOffset + 8);
226
+ } else if ((nameOffset = ua.lastIndexOf(' ') + 1) < (verOffset = ua.lastIndexOf('/'))) {
227
+ // In most other browsers, 'name/version' is at the end of userAgent
228
+ name = ua.substring(nameOffset,verOffset);
229
+ fullVersion = ua.substring(verOffset + 1);
230
+
231
+ if (name.toLowerCase() === name.toUpperCase()) {
232
+ name = navigator.appName;
233
+ }
234
+ }
235
+
236
+ // Trim the fullVersion string at semicolon/space if present
237
+ if ((ix = fullVersion.indexOf(';')) !== -1) {
238
+ fullVersion = fullVersion.substring(0, ix);
239
+ }
240
+ if ((ix = fullVersion.indexOf(' ')) !== -1) {
241
+ fullVersion = fullVersion.substring(0, ix);
242
+ }
243
+
244
+ // Get major version
245
+ majorVersion = parseInt('' + fullVersion, 10);
246
+ if (isNaN(majorVersion)) {
247
+ fullVersion = '' + parseFloat(navigator.appVersion);
248
+ majorVersion = parseInt(navigator.appVersion, 10);
249
+ }
250
+
251
+ // Return data
252
+ return {
253
+ name: name,
254
+ version: majorVersion,
255
+ isIE: isIE,
256
+ isFirefox: isFirefox,
257
+ isChrome: isChrome,
258
+ isSafari: isSafari,
259
+ isIos: /(iPad|iPhone|iPod)/g.test(navigator.platform),
260
+ isIphone: /(iPhone|iPod)/g.test(navigator.userAgent),
261
+ isTouch: 'ontouchstart' in document.documentElement
262
+ };
263
+ }
264
+
265
+ // Check for mime type support against a player instance
266
+ // Credits: http://diveintohtml5.info/everything.html
267
+ // Related: http://www.leanbackplyr.com/test/h5mt.html
268
+ function _supportMime(plyr, mimeType) {
269
+ var media = plyr.media;
270
+
271
+ if (plyr.type === 'video') {
272
+ // Check type
273
+ switch (mimeType) {
274
+ case 'video/webm': return !!(media.canPlayType && media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''));
275
+ case 'video/mp4': return !!(media.canPlayType && media.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''));
276
+ case 'video/ogg': return !!(media.canPlayType && media.canPlayType('video/ogg; codecs="theora"').replace(/no/, ''));
277
+ }
278
+ } else if (plyr.type === 'audio') {
279
+ // Check type
280
+ switch (mimeType) {
281
+ case 'audio/mpeg': return !!(media.canPlayType && media.canPlayType('audio/mpeg;').replace(/no/, ''));
282
+ case 'audio/ogg': return !!(media.canPlayType && media.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
283
+ case 'audio/wav': return !!(media.canPlayType && media.canPlayType('audio/wav; codecs="1"').replace(/no/, ''));
284
+ }
285
+ }
286
+
287
+ // If we got this far, we're stuffed
288
+ return false;
289
+ }
290
+
291
+ // Inject a script
292
+ function _injectScript(source) {
293
+ if (document.querySelectorAll('script[src="' + source + '"]').length) {
294
+ return;
295
+ }
296
+
297
+ var tag = document.createElement('script');
298
+ tag.src = source;
299
+ var firstScriptTag = document.getElementsByTagName('script')[0];
300
+ firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
301
+ }
302
+
303
+ // Element exists in an array
304
+ function _inArray(haystack, needle) {
305
+ return Array.prototype.indexOf && (haystack.indexOf(needle) !== -1);
306
+ }
307
+
308
+ // Replace all
309
+ function _replaceAll(string, find, replace) {
310
+ return string.replace(new RegExp(find.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'), replace);
311
+ }
312
+
313
+ // Wrap an element
314
+ function _wrap(elements, wrapper) {
315
+ // Convert `elements` to an array, if necessary.
316
+ if (!elements.length) {
317
+ elements = [elements];
318
+ }
319
+
320
+ // Loops backwards to prevent having to clone the wrapper on the
321
+ // first element (see `child` below).
322
+ for (var i = elements.length - 1; i >= 0; i--) {
323
+ var child = (i > 0) ? wrapper.cloneNode(true) : wrapper;
324
+ var element = elements[i];
325
+
326
+ // Cache the current parent and sibling.
327
+ var parent = element.parentNode;
328
+ var sibling = element.nextSibling;
329
+
330
+ // Wrap the element (is automatically removed from its current
331
+ // parent).
332
+ child.appendChild(element);
333
+
334
+ // If the element had a sibling, insert the wrapper before
335
+ // the sibling to maintain the HTML structure; otherwise, just
336
+ // append it to the parent.
337
+ if (sibling) {
338
+ parent.insertBefore(child, sibling);
339
+ } else {
340
+ parent.appendChild(child);
341
+ }
342
+
343
+ return child;
344
+ }
345
+ }
346
+
347
+ // Unwrap an element
348
+ // http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/
349
+ /*function _unwrap(wrapper) {
350
+ // Get the element's parent node
351
+ var parent = wrapper.parentNode;
352
+
353
+ // Move all children out of the element
354
+ while (wrapper.firstChild) {
355
+ parent.insertBefore(wrapper.firstChild, wrapper);
356
+ }
357
+
358
+ // Remove the empty element
359
+ parent.removeChild(wrapper);
360
+ }*/
361
+
362
+ // Remove an element
363
+ function _remove(element) {
364
+ if (!element) {
365
+ return;
366
+ }
367
+ element.parentNode.removeChild(element);
368
+ }
369
+
370
+ // Prepend child
371
+ function _prependChild(parent, element) {
372
+ parent.insertBefore(element, parent.firstChild);
373
+ }
374
+
375
+ // Set attributes
376
+ function _setAttributes(element, attributes) {
377
+ for (var key in attributes) {
378
+ element.setAttribute(key, (_is.boolean(attributes[key]) && attributes[key]) ? '' : attributes[key]);
379
+ }
380
+ }
381
+
382
+ // Insert a HTML element
383
+ function _insertElement(type, parent, attributes) {
384
+ // Create a new <element>
385
+ var element = document.createElement(type);
386
+
387
+ // Set all passed attributes
388
+ _setAttributes(element, attributes);
389
+
390
+ // Inject the new element
391
+ _prependChild(parent, element);
392
+ }
393
+
394
+ // Get a classname from selector
395
+ function _getClassname(selector) {
396
+ return selector.replace('.', '');
397
+ }
398
+
399
+ // Toggle class on an element
400
+ function _toggleClass(element, className, state) {
401
+ if (element) {
402
+ if (element.classList) {
403
+ element.classList[state ? 'add' : 'remove'](className);
404
+ } else {
405
+ var name = (' ' + element.className + ' ').replace(/\s+/g, ' ').replace(' ' + className + ' ', '');
406
+ element.className = name + (state ? ' ' + className : '');
407
+ }
408
+ }
409
+ }
410
+
411
+ // Has class name
412
+ function _hasClass(element, className) {
413
+ if (element) {
414
+ if (element.classList) {
415
+ return element.classList.contains(className);
416
+ } else {
417
+ return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
418
+ }
419
+ }
420
+ return false;
421
+ }
422
+
423
+ // Element matches selector
424
+ function _matches(element, selector) {
425
+ var p = Element.prototype;
426
+
427
+ var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
428
+ return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
429
+ };
430
+
431
+ return f.call(element, selector);
432
+ }
433
+
434
+ // Bind along with custom handler
435
+ function _proxyListener(element, eventName, userListener, defaultListener, useCapture) {
436
+ _on(element, eventName, function(event) {
437
+ if (userListener) {
438
+ userListener.apply(element, [event]);
439
+ }
440
+ defaultListener.apply(element, [event]);
441
+ }, useCapture);
442
+ }
443
+
444
+ // Toggle event listener
445
+ function _toggleListener(element, events, callback, toggle, useCapture) {
446
+ var eventList = events.split(' ');
447
+
448
+ // Whether the listener is a capturing listener or not
449
+ // Default to false
450
+ if (!_is.boolean(useCapture)) {
451
+ useCapture = false;
452
+ }
453
+
454
+ // If a nodelist is passed, call itself on each node
455
+ if (element instanceof NodeList) {
456
+ for (var x = 0; x < element.length; x++) {
457
+ if (element[x] instanceof Node) {
458
+ _toggleListener(element[x], arguments[1], arguments[2], arguments[3]);
459
+ }
460
+ }
461
+ return;
462
+ }
463
+
464
+ // If a single node is passed, bind the event listener
465
+ for (var i = 0; i < eventList.length; i++) {
466
+ element[toggle ? 'addEventListener' : 'removeEventListener'](eventList[i], callback, useCapture);
467
+ }
468
+ }
469
+
470
+ // Bind event
471
+ function _on(element, events, callback, useCapture) {
472
+ if (element) {
473
+ _toggleListener(element, events, callback, true, useCapture);
474
+ }
475
+ }
476
+
477
+ // Unbind event
478
+ /*function _off(element, events, callback, useCapture) {
479
+ if (element) {
480
+ _toggleListener(element, events, callback, false, useCapture);
481
+ }
482
+ }*/
483
+
484
+ // Trigger event
485
+ function _event(element, type, bubbles, properties) {
486
+ // Bail if no element
487
+ if (!element || !type) {
488
+ return;
489
+ }
490
+
491
+ // Default bubbles to false
492
+ if (!_is.boolean(bubbles)) {
493
+ bubbles = false;
494
+ }
495
+
496
+ // Create and dispatch the event
497
+ var event = new CustomEvent(type, {
498
+ bubbles: bubbles,
499
+ detail: properties
500
+ });
501
+
502
+ // Dispatch the event
503
+ element.dispatchEvent(event);
504
+ }
505
+
506
+ // Toggle aria-pressed state on a toggle button
507
+ // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
508
+ function _toggleState(target, state) {
509
+ // Bail if no target
510
+ if (!target) {
511
+ return;
512
+ }
513
+
514
+ // Get state
515
+ state = (_is.boolean(state) ? state : !target.getAttribute('aria-pressed'));
516
+
517
+ // Set the attribute on target
518
+ target.setAttribute('aria-pressed', state);
519
+
520
+ return state;
521
+ }
522
+
523
+ // Get percentage
524
+ function _getPercentage(current, max) {
525
+ if (current === 0 || max === 0 || isNaN(current) || isNaN(max)) {
526
+ return 0;
527
+ }
528
+ return ((current / max) * 100).toFixed(2);
529
+ }
530
+
531
+ // Deep extend/merge destination object with N more objects
532
+ // http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
533
+ // Removed call to arguments.callee (used explicit function name instead)
534
+ function _extend() {
535
+ // Get arguments
536
+ var objects = arguments;
537
+
538
+ // Bail if nothing to merge
539
+ if (!objects.length) {
540
+ return;
541
+ }
542
+
543
+ // Return first if specified but nothing to merge
544
+ if (objects.length === 1) {
545
+ return objects[0];
546
+ }
547
+
548
+ // First object is the destination
549
+ var destination = Array.prototype.shift.call(objects),
550
+ length = objects.length;
551
+
552
+ // Loop through all objects to merge
553
+ for (var i = 0; i < length; i++) {
554
+ var source = objects[i];
555
+
556
+ for (var property in source) {
557
+ if (source[property] && source[property].constructor && source[property].constructor === Object) {
558
+ destination[property] = destination[property] || {};
559
+ _extend(destination[property], source[property]);
560
+ } else {
561
+ destination[property] = source[property];
562
+ }
563
+ }
564
+ }
565
+
566
+ return destination;
567
+ }
568
+
569
+ // Check variable types
570
+ var _is = {
571
+ object: function(input) {
572
+ return input !== null && typeof(input) === 'object';
573
+ },
574
+ array: function(input) {
575
+ return input !== null && (typeof(input) === 'object' && input.constructor === Array);
576
+ },
577
+ number: function(input) {
578
+ return input !== null && (typeof(input) === 'number' && !isNaN(input - 0) || (typeof input === 'object' && input.constructor === Number));
579
+ },
580
+ string: function(input) {
581
+ return input !== null && (typeof input === 'string' || (typeof input === 'object' && input.constructor === String));
582
+ },
583
+ boolean: function(input) {
584
+ return input !== null && typeof input === 'boolean';
585
+ },
586
+ nodeList: function(input) {
587
+ return input !== null && input instanceof NodeList;
588
+ },
589
+ htmlElement: function(input) {
590
+ return input !== null && input instanceof HTMLElement;
591
+ },
592
+ function: function(input) {
593
+ return input !== null && typeof input === 'function';
594
+ },
595
+ undefined: function(input) {
596
+ return input !== null && typeof input === 'undefined';
597
+ }
598
+ };
599
+
600
+ // Parse YouTube ID from url
601
+ function _parseYouTubeId(url) {
602
+ var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
603
+ return (url.match(regex)) ? RegExp.$2 : url;
604
+ }
605
+
606
+ // Parse Vimeo ID from url
607
+ function _parseVimeoId(url) {
608
+ var regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
609
+ return (url.match(regex)) ? RegExp.$2 : url;
610
+ }
611
+
612
+ // Fullscreen API
613
+ function _fullscreen() {
614
+ var fullscreen = {
615
+ supportsFullScreen: false,
616
+ isFullScreen: function() { return false; },
617
+ requestFullScreen: function() {},
618
+ cancelFullScreen: function() {},
619
+ fullScreenEventName: '',
620
+ element: null,
621
+ prefix: ''
622
+ },
623
+ browserPrefixes = 'webkit o moz ms khtml'.split(' ');
624
+
625
+ // Check for native support
626
+ if (!_is.undefined(document.cancelFullScreen)) {
627
+ fullscreen.supportsFullScreen = true;
628
+ } else {
629
+ // Check for fullscreen support by vendor prefix
630
+ for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
631
+ fullscreen.prefix = browserPrefixes[i];
632
+
633
+ if (!_is.undefined(document[fullscreen.prefix + 'CancelFullScreen'])) {
634
+ fullscreen.supportsFullScreen = true;
635
+ break;
636
+ } else if (!_is.undefined(document.msExitFullscreen) && document.msFullscreenEnabled) {
637
+ // Special case for MS (when isn't it?)
638
+ fullscreen.prefix = 'ms';
639
+ fullscreen.supportsFullScreen = true;
640
+ break;
641
+ }
642
+ }
643
+ }
644
+
645
+ // Update methods to do something useful
646
+ if (fullscreen.supportsFullScreen) {
647
+ // Yet again Microsoft awesomeness,
648
+ // Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
649
+ fullscreen.fullScreenEventName = (fullscreen.prefix === 'ms' ? 'MSFullscreenChange' : fullscreen.prefix + 'fullscreenchange');
650
+
651
+ fullscreen.isFullScreen = function(element) {
652
+ if (_is.undefined(element)) {
653
+ element = document.body;
654
+ }
655
+ switch (this.prefix) {
656
+ case '':
657
+ return document.fullscreenElement === element;
658
+ case 'moz':
659
+ return document.mozFullScreenElement === element;
660
+ default:
661
+ return document[this.prefix + 'FullscreenElement'] === element;
662
+ }
663
+ };
664
+ fullscreen.requestFullScreen = function(element) {
665
+ if (_is.undefined(element)) {
666
+ element = document.body;
667
+ }
668
+ return (this.prefix === '') ? element.requestFullScreen() : element[this.prefix + (this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
669
+ };
670
+ fullscreen.cancelFullScreen = function() {
671
+ return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + (this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
672
+ };
673
+ fullscreen.element = function() {
674
+ return (this.prefix === '') ? document.fullscreenElement : document[this.prefix + 'FullscreenElement'];
675
+ };
676
+ }
677
+
678
+ return fullscreen;
679
+ }
680
+
681
+ // Local storage
682
+ var _storage = {
683
+ supported: (function() {
684
+ if (!('localStorage' in window)) {
685
+ return false;
686
+ }
687
+
688
+ // Try to use it (it might be disabled, e.g. user is in private/porn mode)
689
+ // see: https://github.com/Selz/plyr/issues/131
690
+ try {
691
+ // Add test item
692
+ window.localStorage.setItem('___test', 'OK');
693
+
694
+ // Get the test item
695
+ var result = window.localStorage.getItem('___test');
696
+
697
+ // Clean up
698
+ window.localStorage.removeItem('___test');
699
+
700
+ // Check if value matches
701
+ return (result === 'OK');
702
+ }
703
+ catch (e) {
704
+ return false;
705
+ }
706
+
707
+ return false;
708
+ })()
709
+ };
710
+
711
+ // Player instance
712
+ function Plyr(media, config) {
713
+ var plyr = this,
714
+ timers = {},
715
+ api;
716
+
717
+ // Set media
718
+ plyr.media = media;
719
+ var original = media.cloneNode(true);
720
+
721
+ // Trigger events, with plyr instance passed
722
+ function _triggerEvent(element, type, bubbles, properties) {
723
+ _event(element, type, bubbles, _extend({}, properties, {
724
+ plyr: api
725
+ }));
726
+ }
727
+
728
+ // Debugging
729
+ function _console(type, args) {
730
+ if (config.debug && window.console) {
731
+ args = Array.prototype.slice.call(args);
732
+
733
+ if (_is.string(config.logPrefix) && config.logPrefix.length) {
734
+ args.unshift(config.logPrefix);
735
+ }
736
+
737
+ console[type].apply(console, args);
738
+ }
739
+ }
740
+ var _log = function() { _console('log', arguments) },
741
+ _warn = function() { _console('warn', arguments) };
742
+
743
+ // Log config options
744
+ _log('Config', config);
745
+
746
+ // Get icon URL
747
+ function _getIconUrl() {
748
+ return {
749
+ url: config.iconUrl,
750
+ absolute: (config.iconUrl.indexOf("http") === 0) || plyr.browser.isIE
751
+ };
752
+ }
753
+
754
+ // Build the default HTML
755
+ function _buildControls() {
756
+ // Create html array
757
+ var html = [],
758
+ iconUrl = _getIconUrl(),
759
+ iconPath = (!iconUrl.absolute ? iconUrl.url : '') + '#' + config.iconPrefix;
760
+
761
+ // Larger overlaid play button
762
+ if (_inArray(config.controls, 'play-large')) {
763
+ html.push(
764
+ '<button type="button" data-plyr="play" class="plyr__play-large">',
765
+ '<svg><use xlink:href="' + iconPath + '-play" /></svg>',
766
+ '<span class="plyr__sr-only">' + config.i18n.play + '</span>',
767
+ '</button>'
768
+ );
769
+ }
770
+
771
+ html.push('<div class="plyr__controls">');
772
+
773
+ // Restart button
774
+ if (_inArray(config.controls, 'restart')) {
775
+ html.push(
776
+ '<button type="button" data-plyr="restart">',
777
+ '<svg><use xlink:href="' + iconPath + '-restart" /></svg>',
778
+ '<span class="plyr__sr-only">' + config.i18n.restart + '</span>',
779
+ '</button>'
780
+ );
781
+ }
782
+
783
+ // Rewind button
784
+ if (_inArray(config.controls, 'rewind')) {
785
+ html.push(
786
+ '<button type="button" data-plyr="rewind">',
787
+ '<svg><use xlink:href="' + iconPath + '-rewind" /></svg>',
788
+ '<span class="plyr__sr-only">' + config.i18n.rewind + '</span>',
789
+ '</button>'
790
+ );
791
+ }
792
+
793
+ // Play Pause button
794
+ // TODO: This should be a toggle button really?
795
+ if (_inArray(config.controls, 'play')) {
796
+ html.push(
797
+ '<button type="button" data-plyr="play">',
798
+ '<svg><use xlink:href="' + iconPath + '-play" /></svg>',
799
+ '<span class="plyr__sr-only">' + config.i18n.play + '</span>',
800
+ '</button>',
801
+ '<button type="button" data-plyr="pause">',
802
+ '<svg><use xlink:href="' + iconPath + '-pause" /></svg>',
803
+ '<span class="plyr__sr-only">' + config.i18n.pause + '</span>',
804
+ '</button>'
805
+ );
806
+ }
807
+
808
+ // Fast forward button
809
+ if (_inArray(config.controls, 'fast-forward')) {
810
+ html.push(
811
+ '<button type="button" data-plyr="fast-forward">',
812
+ '<svg><use xlink:href="' + iconPath + '-fast-forward" /></svg>',
813
+ '<span class="plyr__sr-only">' + config.i18n.forward + '</span>',
814
+ '</button>'
815
+ );
816
+ }
817
+
818
+ // Progress
819
+ if (_inArray(config.controls, 'progress')) {
820
+ // Create progress
821
+ html.push('<span class="plyr__progress">',
822
+ '<label for="seek{id}" class="plyr__sr-only">Seek</label>',
823
+ '<input id="seek{id}" class="plyr__progress--seek" type="range" min="0" max="100" step="0.1" value="0" data-plyr="seek">',
824
+ '<progress class="plyr__progress--played" max="100" value="0" role="presentation"></progress>',
825
+ '<progress class="plyr__progress--buffer" max="100" value="0">',
826
+ '<span>0</span>% ' + config.i18n.buffered,
827
+ '</progress>');
828
+
829
+ // Seek tooltip
830
+ if (config.tooltips.seek) {
831
+ html.push('<span class="plyr__tooltip">00:00</span>');
832
+ }
833
+
834
+ // Close
835
+ html.push('</span>');
836
+ }
837
+
838
+ // Media current time display
839
+ if (_inArray(config.controls, 'current-time')) {
840
+ html.push(
841
+ '<span class="plyr__time">',
842
+ '<span class="plyr__sr-only">' + config.i18n.currentTime + '</span>',
843
+ '<span class="plyr__time--current">00:00</span>',
844
+ '</span>'
845
+ );
846
+ }
847
+
848
+ // Media duration display
849
+ if (_inArray(config.controls, 'duration')) {
850
+ html.push(
851
+ '<span class="plyr__time">',
852
+ '<span class="plyr__sr-only">' + config.i18n.duration + '</span>',
853
+ '<span class="plyr__time--duration">00:00</span>',
854
+ '</span>'
855
+ );
856
+ }
857
+
858
+ // Toggle mute button
859
+ if (_inArray(config.controls, 'mute')) {
860
+ html.push(
861
+ '<button type="button" data-plyr="mute">',
862
+ '<svg class="icon--muted"><use xlink:href="' + iconPath + '-muted" /></svg>',
863
+ '<svg><use xlink:href="' + iconPath + '-volume" /></svg>',
864
+ '<span class="plyr__sr-only">' + config.i18n.toggleMute + '</span>',
865
+ '</button>'
866
+ );
867
+ }
868
+
869
+ // Volume range control
870
+ if (_inArray(config.controls, 'volume')) {
871
+ html.push(
872
+ '<span class="plyr__volume">',
873
+ '<label for="volume{id}" class="plyr__sr-only">' + config.i18n.volume + '</label>',
874
+ '<input id="volume{id}" class="plyr__volume--input" type="range" min="' + config.volumeMin + '" max="' + config.volumeMax + '" value="' + config.volume + '" data-plyr="volume">',
875
+ '<progress class="plyr__volume--display" max="' + config.volumeMax + '" value="' + config.volumeMin + '" role="presentation"></progress>',
876
+ '</span>'
877
+ );
878
+ }
879
+
880
+ // Toggle captions button
881
+ if (_inArray(config.controls, 'captions')) {
882
+ html.push(
883
+ '<button type="button" data-plyr="captions">',
884
+ '<svg class="icon--captions-on"><use xlink:href="' + iconPath + '-captions-on" /></svg>',
885
+ '<svg><use xlink:href="' + iconPath+ '-captions-off" /></svg>',
886
+ '<span class="plyr__sr-only">' + config.i18n.toggleCaptions + '</span>',
887
+ '</button>'
888
+ );
889
+ }
890
+
891
+ // Toggle fullscreen button
892
+ if (_inArray(config.controls, 'fullscreen')) {
893
+ html.push(
894
+ '<button type="button" data-plyr="fullscreen">',
895
+ '<svg class="icon--exit-fullscreen"><use xlink:href="' + iconPath + '-exit-fullscreen" /></svg>',
896
+ '<svg><use xlink:href="' + iconPath + '-enter-fullscreen" /></svg>',
897
+ '<span class="plyr__sr-only">' + config.i18n.toggleFullscreen + '</span>',
898
+ '</button>'
899
+ );
900
+ }
901
+
902
+ // Close everything
903
+ html.push('</div>');
904
+
905
+ return html.join('');
906
+ }
907
+
908
+ // Setup fullscreen
909
+ function _setupFullscreen() {
910
+ if (!plyr.supported.full) {
911
+ return;
912
+ }
913
+
914
+ if ((plyr.type !== 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) {
915
+ // Check for native support
916
+ var nativeSupport = fullscreen.supportsFullScreen;
917
+
918
+ if (nativeSupport || (config.fullscreen.fallback && !_inFrame())) {
919
+ _log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled');
920
+
921
+ // Add styling hook
922
+ _toggleClass(plyr.container, config.classes.fullscreen.enabled, true);
923
+ } else {
924
+ _log('Fullscreen not supported and fallback disabled');
925
+ }
926
+
927
+ // Toggle state
928
+ if (plyr.buttons && plyr.buttons.fullscreen) {
929
+ _toggleState(plyr.buttons.fullscreen, false);
930
+ }
931
+
932
+ // Setup focus trap
933
+ _focusTrap();
934
+ }
935
+ }
936
+
937
+ // Setup captions
938
+ function _setupCaptions() {
939
+ // Bail if not HTML5 video
940
+ if (plyr.type !== 'video') {
941
+ return;
942
+ }
943
+
944
+ // Inject the container
945
+ if (!_getElement(config.selectors.captions)) {
946
+ plyr.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + _getClassname(config.selectors.captions) + '"></div>');
947
+ }
948
+
949
+ // Determine if HTML5 textTracks is supported
950
+ plyr.usingTextTracks = false;
951
+ if (plyr.media.textTracks) {
952
+ plyr.usingTextTracks = true;
953
+ }
954
+
955
+ // Get URL of caption file if exists
956
+ var captionSrc = '',
957
+ kind,
958
+ children = plyr.media.childNodes;
959
+
960
+ for (var i = 0; i < children.length; i++) {
961
+ if (children[i].nodeName.toLowerCase() === 'track') {
962
+ kind = children[i].kind;
963
+ if (kind === 'captions' || kind === 'subtitles') {
964
+ captionSrc = children[i].getAttribute('src');
965
+ }
966
+ }
967
+ }
968
+
969
+ // Record if caption file exists or not
970
+ plyr.captionExists = true;
971
+ if (captionSrc === '') {
972
+ plyr.captionExists = false;
973
+ _log('No caption track found');
974
+ } else {
975
+ _log('Caption track found; URI: ' + captionSrc);
976
+ }
977
+
978
+ // If no caption file exists, hide container for caption text
979
+ if (!plyr.captionExists) {
980
+ _toggleClass(plyr.container, config.classes.captions.enabled);
981
+ } else {
982
+ // Turn off native caption rendering to avoid double captions
983
+ // This doesn't seem to work in Safari 7+, so the <track> elements are removed from the dom below
984
+ var tracks = plyr.media.textTracks;
985
+ for (var x = 0; x < tracks.length; x++) {
986
+ tracks[x].mode = 'hidden';
987
+ }
988
+
989
+ // Enable UI
990
+ _showCaptions(plyr);
991
+
992
+ // Disable unsupported browsers than report false positive
993
+ // Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1033144
994
+ if ((plyr.browser.isIE && plyr.browser.version >= 10) ||
995
+ (plyr.browser.isFirefox && plyr.browser.version >= 31)) {
996
+
997
+ // Debugging
998
+ _log('Detected browser with known TextTrack issues - using manual fallback');
999
+
1000
+ // Set to false so skips to 'manual' captioning
1001
+ plyr.usingTextTracks = false;
1002
+ }
1003
+
1004
+ // Rendering caption tracks
1005
+ // Native support required - http://caniuse.com/webvtt
1006
+ if (plyr.usingTextTracks) {
1007
+ _log('TextTracks supported');
1008
+
1009
+ for (var y = 0; y < tracks.length; y++) {
1010
+ var track = tracks[y];
1011
+
1012
+ if (track.kind === 'captions' || track.kind === 'subtitles') {
1013
+ _on(track, 'cuechange', function() {
1014
+ // Display a cue, if there is one
1015
+ if (this.activeCues[0] && 'text' in this.activeCues[0]) {
1016
+ _setCaption(this.activeCues[0].getCueAsHTML());
1017
+ } else {
1018
+ _setCaption();
1019
+ }
1020
+ });
1021
+ }
1022
+ }
1023
+ } else {
1024
+ // Caption tracks not natively supported
1025
+ _log('TextTracks not supported so rendering captions manually');
1026
+
1027
+ // Render captions from array at appropriate time
1028
+ plyr.currentCaption = '';
1029
+ plyr.captions = [];
1030
+
1031
+ if (captionSrc !== '') {
1032
+ // Create XMLHttpRequest Object
1033
+ var xhr = new XMLHttpRequest();
1034
+
1035
+ xhr.onreadystatechange = function() {
1036
+ if (xhr.readyState === 4) {
1037
+ if (xhr.status === 200) {
1038
+ var captions = [],
1039
+ caption,
1040
+ req = xhr.responseText;
1041
+
1042
+ //According to webvtt spec, line terminator consists of one of the following
1043
+ // CRLF (U+000D U+000A), LF (U+000A) or CR (U+000D)
1044
+ var lineSeparator = '\r\n';
1045
+ if(req.indexOf(lineSeparator+lineSeparator) === -1) {
1046
+ if(req.indexOf('\r\r') !== -1){
1047
+ lineSeparator = '\r';
1048
+ } else {
1049
+ lineSeparator = '\n';
1050
+ }
1051
+ }
1052
+
1053
+ captions = req.split(lineSeparator+lineSeparator);
1054
+
1055
+ for (var r = 0; r < captions.length; r++) {
1056
+ caption = captions[r];
1057
+ plyr.captions[r] = [];
1058
+
1059
+ // Get the parts of the captions
1060
+ var parts = caption.split(lineSeparator),
1061
+ index = 0;
1062
+
1063
+ // Incase caption numbers are added
1064
+ if (parts[index].indexOf(":") === -1) {
1065
+ index = 1;
1066
+ }
1067
+
1068
+ plyr.captions[r] = [parts[index], parts[index + 1]];
1069
+ }
1070
+
1071
+ // Remove first element ('VTT')
1072
+ plyr.captions.shift();
1073
+
1074
+ _log('Successfully loaded the caption file via AJAX');
1075
+ } else {
1076
+ _warn(config.logPrefix + 'There was a problem loading the caption file via AJAX');
1077
+ }
1078
+ }
1079
+ };
1080
+
1081
+ xhr.open('get', captionSrc, true);
1082
+
1083
+ xhr.send();
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+
1089
+ // Set the current caption
1090
+ function _setCaption(caption) {
1091
+ /* jshint unused:false */
1092
+ var container = _getElement(config.selectors.captions),
1093
+ content = document.createElement('span');
1094
+
1095
+ // Empty the container
1096
+ container.innerHTML = '';
1097
+
1098
+ // Default to empty
1099
+ if (_is.undefined(caption)) {
1100
+ caption = '';
1101
+ }
1102
+
1103
+ // Set the span content
1104
+ if (_is.string(caption)) {
1105
+ content.innerHTML = caption.trim();
1106
+ } else {
1107
+ content.appendChild(caption);
1108
+ }
1109
+
1110
+ // Set new caption text
1111
+ container.appendChild(content);
1112
+
1113
+ // Force redraw (for Safari)
1114
+ var redraw = container.offsetHeight;
1115
+ }
1116
+
1117
+ // Captions functions
1118
+ // Seek the manual caption time and update UI
1119
+ function _seekManualCaptions(time) {
1120
+ // Utilities for caption time codes
1121
+ function _timecodeCommon(tc, pos) {
1122
+ var tcpair = [];
1123
+ tcpair = tc.split(' --> ');
1124
+ for(var i = 0; i < tcpair.length; i++) {
1125
+ // WebVTT allows for extra meta data after the timestamp line
1126
+ // So get rid of this if it exists
1127
+ tcpair[i] = tcpair[i].replace(/(\d+:\d+:\d+\.\d+).*/, "$1");
1128
+ }
1129
+ return _subTcSecs(tcpair[pos]);
1130
+ }
1131
+ function _timecodeMin(tc) {
1132
+ return _timecodeCommon(tc, 0);
1133
+ }
1134
+ function _timecodeMax(tc) {
1135
+ return _timecodeCommon(tc, 1);
1136
+ }
1137
+ function _subTcSecs(tc) {
1138
+ if (tc === null || tc === undefined) {
1139
+ return 0;
1140
+ } else {
1141
+ var tc1 = [],
1142
+ tc2 = [],
1143
+ seconds;
1144
+ tc1 = tc.split(',');
1145
+ tc2 = tc1[0].split(':');
1146
+ seconds = Math.floor(tc2[0]*60*60) + Math.floor(tc2[1]*60) + Math.floor(tc2[2]);
1147
+ return seconds;
1148
+ }
1149
+ }
1150
+
1151
+ // If it's not video, or we're using textTracks, bail.
1152
+ if (plyr.usingTextTracks || plyr.type !== 'video' || !plyr.supported.full) {
1153
+ return;
1154
+ }
1155
+
1156
+ // Reset subcount
1157
+ plyr.subcount = 0;
1158
+
1159
+ // Check time is a number, if not use currentTime
1160
+ // IE has a bug where currentTime doesn't go to 0
1161
+ // https://twitter.com/Sam_Potts/status/573715746506731521
1162
+ time = _is.number(time) ? time : plyr.media.currentTime;
1163
+
1164
+ // If there's no subs available, bail
1165
+ if (!plyr.captions[plyr.subcount]) {
1166
+ return;
1167
+ }
1168
+
1169
+ while (_timecodeMax(plyr.captions[plyr.subcount][0]) < time.toFixed(1)) {
1170
+ plyr.subcount++;
1171
+ if (plyr.subcount > plyr.captions.length - 1) {
1172
+ plyr.subcount = plyr.captions.length - 1;
1173
+ break;
1174
+ }
1175
+ }
1176
+
1177
+ // Check if the next caption is in the current time range
1178
+ if (plyr.media.currentTime.toFixed(1) >= _timecodeMin(plyr.captions[plyr.subcount][0]) &&
1179
+ plyr.media.currentTime.toFixed(1) <= _timecodeMax(plyr.captions[plyr.subcount][0])) {
1180
+ plyr.currentCaption = plyr.captions[plyr.subcount][1];
1181
+
1182
+ // Render the caption
1183
+ _setCaption(plyr.currentCaption);
1184
+ } else {
1185
+ _setCaption();
1186
+ }
1187
+ }
1188
+
1189
+ // Display captions container and button (for initialization)
1190
+ function _showCaptions() {
1191
+ // If there's no caption toggle, bail
1192
+ if (!plyr.buttons.captions) {
1193
+ return;
1194
+ }
1195
+
1196
+ _toggleClass(plyr.container, config.classes.captions.enabled, true);
1197
+
1198
+ // Try to load the value from storage
1199
+ var active = plyr.storage.captionsEnabled;
1200
+
1201
+ // Otherwise fall back to the default config
1202
+ if (!_is.boolean(active)) {
1203
+ active = config.captions.defaultActive;
1204
+ }
1205
+
1206
+ if (active) {
1207
+ _toggleClass(plyr.container, config.classes.captions.active, true);
1208
+ _toggleState(plyr.buttons.captions, true);
1209
+ }
1210
+ }
1211
+
1212
+ // Find all elements
1213
+ function _getElements(selector) {
1214
+ return plyr.container.querySelectorAll(selector);
1215
+ }
1216
+
1217
+ // Find a single element
1218
+ function _getElement(selector) {
1219
+ return _getElements(selector)[0];
1220
+ }
1221
+
1222
+ // Determine if we're in an iframe
1223
+ function _inFrame() {
1224
+ try {
1225
+ return window.self !== window.top;
1226
+ }
1227
+ catch (e) {
1228
+ return true;
1229
+ }
1230
+ }
1231
+
1232
+ // Trap focus inside container
1233
+ function _focusTrap() {
1234
+ var tabbables = _getElements('input:not([disabled]), button:not([disabled])'),
1235
+ first = tabbables[0],
1236
+ last = tabbables[tabbables.length - 1];
1237
+
1238
+ function _checkFocus(event) {
1239
+ // If it is TAB
1240
+ if (event.which === 9 && plyr.isFullscreen) {
1241
+ if (event.target === last && !event.shiftKey) {
1242
+ // Move focus to first element that can be tabbed if Shift isn't used
1243
+ event.preventDefault();
1244
+ first.focus();
1245
+ } else if (event.target === first && event.shiftKey) {
1246
+ // Move focus to last element that can be tabbed if Shift is used
1247
+ event.preventDefault();
1248
+ last.focus();
1249
+ }
1250
+ }
1251
+ }
1252
+
1253
+ // Bind the handler
1254
+ _on(plyr.container, 'keydown', _checkFocus);
1255
+ }
1256
+
1257
+ // Add elements to HTML5 media (source, tracks, etc)
1258
+ function _insertChildElements(type, attributes) {
1259
+ if (_is.string(attributes)) {
1260
+ _insertElement(type, plyr.media, { src: attributes });
1261
+ } else if (attributes.constructor === Array) {
1262
+ for (var i = attributes.length - 1; i >= 0; i--) {
1263
+ _insertElement(type, plyr.media, attributes[i]);
1264
+ }
1265
+ }
1266
+ }
1267
+
1268
+ // Insert controls
1269
+ function _injectControls() {
1270
+ // Sprite
1271
+ if (config.loadSprite) {
1272
+ var iconUrl = _getIconUrl();
1273
+
1274
+ // Only load external sprite using AJAX
1275
+ if (iconUrl.absolute) {
1276
+ _log('AJAX loading absolute SVG sprite' + (plyr.browser.isIE ? ' (due to IE)' : ''));
1277
+ loadSprite(iconUrl.url, "sprite-plyr");
1278
+ } else {
1279
+ _log('Sprite will be used as external resource directly');
1280
+ }
1281
+ }
1282
+
1283
+ // Make a copy of the html
1284
+ var html = config.html;
1285
+
1286
+ // Insert custom video controls
1287
+ _log('Injecting custom controls');
1288
+
1289
+ // If no controls are specified, create default
1290
+ if (!html) {
1291
+ html = _buildControls();
1292
+ }
1293
+
1294
+ // Replace seek time instances
1295
+ html = _replaceAll(html, '{seektime}', config.seekTime);
1296
+
1297
+ // Replace all id references with random numbers
1298
+ html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000)));
1299
+
1300
+ // Controls container
1301
+ var target;
1302
+
1303
+ // Inject to custom location
1304
+ if (_is.string(config.selectors.controls.container)) {
1305
+ target = document.querySelector(config.selectors.controls.container);
1306
+ }
1307
+
1308
+ // Inject into the container by default
1309
+ if (!_is.htmlElement(target)) {
1310
+ target = plyr.container
1311
+ }
1312
+
1313
+ // Inject controls HTML
1314
+ target.insertAdjacentHTML('beforeend', html);
1315
+
1316
+ // Setup tooltips
1317
+ if (config.tooltips.controls) {
1318
+ var labels = _getElements([config.selectors.controls.wrapper, ' ', config.selectors.labels, ' .', config.classes.hidden].join(''));
1319
+
1320
+ for (var i = labels.length - 1; i >= 0; i--) {
1321
+ var label = labels[i];
1322
+
1323
+ _toggleClass(label, config.classes.hidden, false);
1324
+ _toggleClass(label, config.classes.tooltip, true);
1325
+ }
1326
+ }
1327
+ }
1328
+
1329
+ // Find the UI controls and store references
1330
+ function _findElements() {
1331
+ try {
1332
+ plyr.controls = _getElement(config.selectors.controls.wrapper);
1333
+
1334
+ // Buttons
1335
+ plyr.buttons = {};
1336
+ plyr.buttons.seek = _getElement(config.selectors.buttons.seek);
1337
+ plyr.buttons.play = _getElements(config.selectors.buttons.play);
1338
+ plyr.buttons.pause = _getElement(config.selectors.buttons.pause);
1339
+ plyr.buttons.restart = _getElement(config.selectors.buttons.restart);
1340
+ plyr.buttons.rewind = _getElement(config.selectors.buttons.rewind);
1341
+ plyr.buttons.forward = _getElement(config.selectors.buttons.forward);
1342
+ plyr.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
1343
+
1344
+ // Inputs
1345
+ plyr.buttons.mute = _getElement(config.selectors.buttons.mute);
1346
+ plyr.buttons.captions = _getElement(config.selectors.buttons.captions);
1347
+
1348
+ // Progress
1349
+ plyr.progress = {};
1350
+ plyr.progress.container = _getElement(config.selectors.progress.container);
1351
+
1352
+ // Progress - Buffering
1353
+ plyr.progress.buffer = {};
1354
+ plyr.progress.buffer.bar = _getElement(config.selectors.progress.buffer);
1355
+ plyr.progress.buffer.text = plyr.progress.buffer.bar && plyr.progress.buffer.bar.getElementsByTagName('span')[0];
1356
+
1357
+ // Progress - Played
1358
+ plyr.progress.played = _getElement(config.selectors.progress.played);
1359
+
1360
+ // Seek tooltip
1361
+ plyr.progress.tooltip = plyr.progress.container && plyr.progress.container.querySelector('.' + config.classes.tooltip);
1362
+
1363
+ // Volume
1364
+ plyr.volume = {};
1365
+ plyr.volume.input = _getElement(config.selectors.volume.input);
1366
+ plyr.volume.display = _getElement(config.selectors.volume.display);
1367
+
1368
+ // Timing
1369
+ plyr.duration = _getElement(config.selectors.duration);
1370
+ plyr.currentTime = _getElement(config.selectors.currentTime);
1371
+ plyr.seekTime = _getElements(config.selectors.seekTime);
1372
+
1373
+ return true;
1374
+ }
1375
+ catch(e) {
1376
+ _warn('It looks like there is a problem with your controls HTML');
1377
+
1378
+ // Restore native video controls
1379
+ _toggleNativeControls(true);
1380
+
1381
+ return false;
1382
+ }
1383
+ }
1384
+
1385
+ // Toggle style hook
1386
+ function _toggleStyleHook() {
1387
+ _toggleClass(plyr.container, config.selectors.container.replace('.', ''), plyr.supported.full);
1388
+ }
1389
+
1390
+ // Toggle native controls
1391
+ function _toggleNativeControls(toggle) {
1392
+ if (toggle && _inArray(config.types.html5, plyr.type)) {
1393
+ plyr.media.setAttribute('controls', '');
1394
+ } else {
1395
+ plyr.media.removeAttribute('controls');
1396
+ }
1397
+ }
1398
+
1399
+ // Setup aria attribute for play and iframe title
1400
+ function _setTitle(iframe) {
1401
+ // Find the current text
1402
+ var label = config.i18n.play;
1403
+
1404
+ // If there's a media title set, use that for the label
1405
+ if (_is.string(config.title) && config.title.length) {
1406
+ label += ', ' + config.title;
1407
+
1408
+ // Set container label
1409
+ plyr.container.setAttribute('aria-label', config.title);
1410
+ }
1411
+
1412
+ // If there's a play button, set label
1413
+ if (plyr.supported.full && plyr.buttons.play) {
1414
+ for (var i = plyr.buttons.play.length - 1; i >= 0; i--) {
1415
+ plyr.buttons.play[i].setAttribute('aria-label', label);
1416
+ }
1417
+ }
1418
+
1419
+ // Set iframe title
1420
+ // https://github.com/Selz/plyr/issues/124
1421
+ if (_is.htmlElement(iframe)) {
1422
+ iframe.setAttribute('title', config.i18n.frameTitle.replace('{title}', config.title));
1423
+ }
1424
+ }
1425
+
1426
+ // Setup localStorage
1427
+ function _setupStorage() {
1428
+ var value = null;
1429
+ plyr.storage = {};
1430
+
1431
+ // Bail if we don't have localStorage support or it's disabled
1432
+ if (!_storage.supported || !config.storage.enabled) {
1433
+ return;
1434
+ }
1435
+
1436
+ // Clean up old volume
1437
+ // https://github.com/Selz/plyr/issues/171
1438
+ window.localStorage.removeItem('plyr-volume');
1439
+
1440
+ // load value from the current key
1441
+ value = window.localStorage.getItem(config.storage.key);
1442
+
1443
+ if (!value) {
1444
+ // Key wasn't set (or had been cleared), move along
1445
+ return;
1446
+ } else if (/^\d+(\.\d+)?$/.test(value)) {
1447
+ // If value is a number, it's probably volume from an older
1448
+ // version of plyr. See: https://github.com/Selz/plyr/pull/313
1449
+ // Update the key to be JSON
1450
+ _updateStorage({volume: parseFloat(value)});
1451
+ } else {
1452
+ // Assume it's JSON from this or a later version of plyr
1453
+ plyr.storage = JSON.parse(value);
1454
+ }
1455
+ }
1456
+
1457
+ // Save a value back to local storage
1458
+ function _updateStorage(value) {
1459
+ // Bail if we don't have localStorage support or it's disabled
1460
+ if (!_storage.supported || !config.storage.enabled) {
1461
+ return;
1462
+ }
1463
+
1464
+ // Update the working copy of the values
1465
+ _extend(plyr.storage, value);
1466
+
1467
+ // Update storage
1468
+ window.localStorage.setItem(config.storage.key, JSON.stringify(plyr.storage));
1469
+ }
1470
+
1471
+ // Setup media
1472
+ function _setupMedia() {
1473
+ // If there's no media, bail
1474
+ if (!plyr.media) {
1475
+ _warn('No media element found!');
1476
+ return;
1477
+ }
1478
+
1479
+ if (plyr.supported.full) {
1480
+ // Add type class
1481
+ _toggleClass(plyr.container, config.classes.type.replace('{0}', plyr.type), true);
1482
+
1483
+ // Add video class for embeds
1484
+ // This will require changes if audio embeds are added
1485
+ if (_inArray(config.types.embed, plyr.type)) {
1486
+ _toggleClass(plyr.container, config.classes.type.replace('{0}', 'video'), true);
1487
+ }
1488
+
1489
+ // If there's no autoplay attribute, assume the video is stopped and add state class
1490
+ _toggleClass(plyr.container, config.classes.stopped, config.autoplay);
1491
+
1492
+ // Add iOS class
1493
+ _toggleClass(plyr.ontainer, config.classes.isIos, plyr.browser.isIos);
1494
+
1495
+ // Add touch class
1496
+ _toggleClass(plyr.container, config.classes.isTouch, plyr.browser.isTouch);
1497
+
1498
+ // Inject the player wrapper
1499
+ if (plyr.type === 'video') {
1500
+ // Create the wrapper div
1501
+ var wrapper = document.createElement('div');
1502
+ wrapper.setAttribute('class', config.classes.videoWrapper);
1503
+
1504
+ // Wrap the video in a container
1505
+ _wrap(plyr.media, wrapper);
1506
+
1507
+ // Cache the container
1508
+ plyr.videoContainer = wrapper;
1509
+ }
1510
+ }
1511
+
1512
+ // Embeds
1513
+ if (_inArray(config.types.embed, plyr.type)) {
1514
+ _setupEmbed();
1515
+ }
1516
+ }
1517
+
1518
+ // Setup YouTube/Vimeo
1519
+ function _setupEmbed() {
1520
+ var container = document.createElement('div'),
1521
+ mediaId,
1522
+ id = plyr.type + '-' + Math.floor(Math.random() * (10000));
1523
+
1524
+ // Parse IDs from URLs if supplied
1525
+ switch (plyr.type) {
1526
+ case 'youtube':
1527
+ mediaId = _parseYouTubeId(plyr.embedId);
1528
+ break;
1529
+
1530
+ case 'vimeo':
1531
+ mediaId = _parseVimeoId(plyr.embedId);
1532
+ break;
1533
+
1534
+ default:
1535
+ mediaId = plyr.embedId;
1536
+ }
1537
+
1538
+ // Remove old containers
1539
+ var containers = _getElements('[id^="' + plyr.type + '-"]');
1540
+ for (var i = containers.length - 1; i >= 0; i--) {
1541
+ _remove(containers[i]);
1542
+ }
1543
+
1544
+ // Add embed class for responsive
1545
+ _toggleClass(plyr.media, config.classes.videoWrapper, true);
1546
+ _toggleClass(plyr.media, config.classes.embedWrapper, true);
1547
+
1548
+ if (plyr.type === 'youtube') {
1549
+ // Create the YouTube container
1550
+ plyr.media.appendChild(container);
1551
+
1552
+ // Set ID
1553
+ container.setAttribute('id', id);
1554
+
1555
+ // Setup API
1556
+ if (_is.object(window.YT)) {
1557
+ _youTubeReady(mediaId, container);
1558
+ } else {
1559
+ // Load the API
1560
+ _injectScript(config.urls.youtube.api);
1561
+
1562
+ // Setup callback for the API
1563
+ window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
1564
+
1565
+ // Add to queue
1566
+ window.onYouTubeReadyCallbacks.push(function() { _youTubeReady(mediaId, container); });
1567
+
1568
+ // Set callback to process queue
1569
+ window.onYouTubeIframeAPIReady = function () {
1570
+ window.onYouTubeReadyCallbacks.forEach(function(callback) { callback(); });
1571
+ };
1572
+ }
1573
+ } else if (plyr.type === 'vimeo') {
1574
+ // Vimeo needs an extra div to hide controls on desktop (which has full support)
1575
+ if (plyr.supported.full) {
1576
+ plyr.media.appendChild(container);
1577
+ } else {
1578
+ container = plyr.media;
1579
+ }
1580
+
1581
+ // Set ID
1582
+ container.setAttribute('id', id);
1583
+
1584
+ // Load the API if not already
1585
+ if (!_is.object(window.Vimeo)) {
1586
+ _injectScript(config.urls.vimeo.api);
1587
+
1588
+ // Wait for fragaloop load
1589
+ var vimeoTimer = window.setInterval(function() {
1590
+ if (_is.object(window.Vimeo)) {
1591
+ window.clearInterval(vimeoTimer);
1592
+ _vimeoReady(mediaId, container);
1593
+ }
1594
+ }, 50);
1595
+ } else {
1596
+ _vimeoReady(mediaId, container);
1597
+ }
1598
+ } else if (plyr.type === 'soundcloud') {
1599
+ // TODO: Currently unsupported and undocumented
1600
+ // Inject the iframe
1601
+ var soundCloud = document.createElement('iframe');
1602
+
1603
+ // Watch for iframe load
1604
+ soundCloud.loaded = false;
1605
+ _on(soundCloud, 'load', function() { soundCloud.loaded = true; });
1606
+
1607
+ _setAttributes(soundCloud, {
1608
+ 'src': 'https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/' + mediaId,
1609
+ 'id': id
1610
+ });
1611
+
1612
+ container.appendChild(soundCloud);
1613
+ plyr.media.appendChild(container);
1614
+
1615
+ // Load the API if not already
1616
+ if (!window.SC) {
1617
+ _injectScript(config.urls.soundcloud.api);
1618
+ }
1619
+
1620
+ // Wait for SC load
1621
+ var soundCloudTimer = window.setInterval(function() {
1622
+ if (window.SC && soundCloud.loaded) {
1623
+ window.clearInterval(soundCloudTimer);
1624
+ _soundcloudReady.call(soundCloud);
1625
+ }
1626
+ }, 50);
1627
+ }
1628
+ }
1629
+
1630
+ // When embeds are ready
1631
+ function _embedReady() {
1632
+ // Setup the UI and call ready if full support
1633
+ if (plyr.supported.full) {
1634
+ _setupInterface();
1635
+ _ready();
1636
+ }
1637
+
1638
+ // Set title
1639
+ _setTitle(_getElement('iframe'));
1640
+ }
1641
+
1642
+ // Handle YouTube API ready
1643
+ function _youTubeReady(videoId, container) {
1644
+ // Setup instance
1645
+ // https://developers.google.com/youtube/iframe_api_reference
1646
+ plyr.embed = new window.YT.Player(container.id, {
1647
+ videoId: videoId,
1648
+ playerVars: {
1649
+ autoplay: (config.autoplay ? 1 : 0),
1650
+ controls: (plyr.supported.full ? 0 : 1),
1651
+ rel: 0,
1652
+ showinfo: 0,
1653
+ iv_load_policy: 3,
1654
+ cc_load_policy: (config.captions.defaultActive ? 1 : 0),
1655
+ cc_lang_pref: 'en',
1656
+ wmode: 'transparent',
1657
+ modestbranding: 1,
1658
+ disablekb: 1,
1659
+ origin: '*' // https://code.google.com/p/gdata-issues/issues/detail?id=5788#c45
1660
+ },
1661
+ events: {
1662
+ 'onError': function(event) {
1663
+ _triggerEvent(plyr.container, 'error', true, {
1664
+ code: event.data,
1665
+ embed: event.target
1666
+ });
1667
+ },
1668
+ 'onReady': function(event) {
1669
+ // Get the instance
1670
+ var instance = event.target;
1671
+
1672
+ // Create a faux HTML5 API using the YouTube API
1673
+ plyr.media.play = function() {
1674
+ instance.playVideo();
1675
+ plyr.media.paused = false;
1676
+ };
1677
+ plyr.media.pause = function() {
1678
+ instance.pauseVideo();
1679
+ plyr.media.paused = true;
1680
+ };
1681
+ plyr.media.stop = function() {
1682
+ instance.stopVideo();
1683
+ plyr.media.paused = true;
1684
+ };
1685
+ plyr.media.duration = instance.getDuration();
1686
+ plyr.media.paused = true;
1687
+ plyr.media.currentTime = 0;
1688
+ plyr.media.muted = instance.isMuted();
1689
+
1690
+ // Set title
1691
+ config.title = instance.getVideoData().title;
1692
+
1693
+ // Set the tabindex
1694
+ if (plyr.supported.full) {
1695
+ plyr.media.querySelector('iframe').setAttribute('tabindex', '-1');
1696
+ }
1697
+
1698
+ // Update UI
1699
+ _embedReady();
1700
+
1701
+ // Trigger timeupdate
1702
+ _triggerEvent(plyr.media, 'timeupdate');
1703
+
1704
+ // Trigger timeupdate
1705
+ _triggerEvent(plyr.media, 'durationchange');
1706
+
1707
+ // Reset timer
1708
+ window.clearInterval(timers.buffering);
1709
+
1710
+ // Setup buffering
1711
+ timers.buffering = window.setInterval(function() {
1712
+ // Get loaded % from YouTube
1713
+ plyr.media.buffered = instance.getVideoLoadedFraction();
1714
+
1715
+ // Trigger progress only when we actually buffer something
1716
+ if (plyr.media.lastBuffered === null || plyr.media.lastBuffered < plyr.media.buffered) {
1717
+ _triggerEvent(plyr.media, 'progress');
1718
+ }
1719
+
1720
+ // Set last buffer point
1721
+ plyr.media.lastBuffered = plyr.media.buffered;
1722
+
1723
+ // Bail if we're at 100%
1724
+ if (plyr.media.buffered === 1) {
1725
+ window.clearInterval(timers.buffering);
1726
+
1727
+ // Trigger event
1728
+ _triggerEvent(plyr.media, 'canplaythrough');
1729
+ }
1730
+ }, 200);
1731
+ },
1732
+ 'onStateChange': function(event) {
1733
+ // Get the instance
1734
+ var instance = event.target;
1735
+
1736
+ // Reset timer
1737
+ window.clearInterval(timers.playing);
1738
+
1739
+ // Handle events
1740
+ // -1 Unstarted
1741
+ // 0 Ended
1742
+ // 1 Playing
1743
+ // 2 Paused
1744
+ // 3 Buffering
1745
+ // 5 Video cued
1746
+ switch (event.data) {
1747
+ case 0:
1748
+ plyr.media.paused = true;
1749
+ _triggerEvent(plyr.media, 'ended');
1750
+ break;
1751
+
1752
+ case 1:
1753
+ plyr.media.paused = false;
1754
+
1755
+ // If we were seeking, fire seeked event
1756
+ if (plyr.media.seeking) {
1757
+ _triggerEvent(plyr.media, 'seeked');
1758
+ }
1759
+
1760
+ plyr.media.seeking = false;
1761
+ _triggerEvent(plyr.media, 'play');
1762
+ _triggerEvent(plyr.media, 'playing');
1763
+
1764
+ // Poll to get playback progress
1765
+ timers.playing = window.setInterval(function() {
1766
+ // Set the current time
1767
+ plyr.media.currentTime = instance.getCurrentTime();
1768
+
1769
+ // Trigger timeupdate
1770
+ _triggerEvent(plyr.media, 'timeupdate');
1771
+ }, 100);
1772
+
1773
+ // Check duration again due to YouTube bug
1774
+ // https://github.com/Selz/plyr/issues/374
1775
+ // https://code.google.com/p/gdata-issues/issues/detail?id=8690
1776
+ if (plyr.media.duration !== instance.getDuration()) {
1777
+ plyr.media.duration = instance.getDuration();
1778
+ _triggerEvent(plyr.media, 'durationchange');
1779
+ }
1780
+
1781
+ break;
1782
+
1783
+ case 2:
1784
+ plyr.media.paused = true;
1785
+ _triggerEvent(plyr.media, 'pause');
1786
+ break;
1787
+ }
1788
+
1789
+ _triggerEvent(plyr.container, 'statechange', false, {
1790
+ code: event.data
1791
+ });
1792
+ }
1793
+ }
1794
+ });
1795
+ }
1796
+
1797
+ // Vimeo ready
1798
+ function _vimeoReady(mediaId, container) {
1799
+ // Setup instance
1800
+ // https://github.com/vimeo/player.js
1801
+ plyr.embed = new window.Vimeo.Player(container, {
1802
+ id: parseInt(mediaId),
1803
+ loop: config.loop,
1804
+ autoplay: config.autoplay,
1805
+ byline: false,
1806
+ portrait: false,
1807
+ title: false
1808
+ });
1809
+
1810
+ // Create a faux HTML5 API using the Vimeo API
1811
+ plyr.media.play = function() {
1812
+ plyr.embed.play();
1813
+ plyr.media.paused = false;
1814
+ };
1815
+ plyr.media.pause = function() {
1816
+ plyr.embed.pause();
1817
+ plyr.media.paused = true;
1818
+ };
1819
+ plyr.media.stop = function() {
1820
+ plyr.embed.stop();
1821
+ plyr.media.paused = true;
1822
+ };
1823
+
1824
+ plyr.media.paused = true;
1825
+ plyr.media.currentTime = 0;
1826
+
1827
+ // Update UI
1828
+ _embedReady();
1829
+
1830
+ plyr.embed.getCurrentTime().then(function(value) {
1831
+ plyr.media.currentTime = value;
1832
+
1833
+ // Trigger timeupdate
1834
+ _triggerEvent(plyr.media, 'timeupdate');
1835
+ });
1836
+
1837
+ plyr.embed.getDuration().then(function(value) {
1838
+ plyr.media.duration = value;
1839
+
1840
+ // Trigger timeupdate
1841
+ _triggerEvent(plyr.media, 'durationchange');
1842
+ });
1843
+
1844
+ // TODO: Captions
1845
+ /*if (config.captions.defaultActive) {
1846
+ plyr.embed.enableTextTrack('en');
1847
+ }*/
1848
+
1849
+ plyr.embed.on('loaded', function() {
1850
+ // Fix keyboard focus issues
1851
+ // https://github.com/Selz/plyr/issues/317
1852
+ if (_is.htmlElement(plyr.embed.element) && plyr.supported.full) {
1853
+ plyr.embed.element.setAttribute('tabindex', '-1');
1854
+ }
1855
+ });
1856
+
1857
+ plyr.embed.on('play', function() {
1858
+ plyr.media.paused = false;
1859
+ _triggerEvent(plyr.media, 'play');
1860
+ _triggerEvent(plyr.media, 'playing');
1861
+ });
1862
+
1863
+ plyr.embed.on('pause', function() {
1864
+ plyr.media.paused = true;
1865
+ _triggerEvent(plyr.media, 'pause');
1866
+ });
1867
+
1868
+ plyr.embed.on('timeupdate', function(data) {
1869
+ plyr.media.seeking = false;
1870
+ plyr.media.currentTime = data.seconds;
1871
+ _triggerEvent(plyr.media, 'timeupdate');
1872
+ });
1873
+
1874
+ plyr.embed.on('progress', function(data) {
1875
+ plyr.media.buffered = data.percent;
1876
+ _triggerEvent(plyr.media, 'progress');
1877
+
1878
+ if (parseInt(data.percent) === 1) {
1879
+ // Trigger event
1880
+ _triggerEvent(plyr.media, 'canplaythrough');
1881
+ }
1882
+ });
1883
+
1884
+ plyr.embed.on('seeked', function() {
1885
+ plyr.media.seeking = false;
1886
+ _triggerEvent(plyr.media, 'seeked');
1887
+ _triggerEvent(plyr.media, 'play');
1888
+ });
1889
+
1890
+ plyr.embed.on('ended', function() {
1891
+ plyr.media.paused = true;
1892
+ _triggerEvent(plyr.media, 'ended');
1893
+ });
1894
+ }
1895
+
1896
+ // Soundcloud ready
1897
+ function _soundcloudReady() {
1898
+ /* jshint validthis: true */
1899
+ plyr.embed = window.SC.Widget(this);
1900
+
1901
+ // Setup on ready
1902
+ plyr.embed.bind(window.SC.Widget.Events.READY, function() {
1903
+ // Create a faux HTML5 API using the Soundcloud API
1904
+ plyr.media.play = function() {
1905
+ plyr.embed.play();
1906
+ plyr.media.paused = false;
1907
+ };
1908
+ plyr.media.pause = function() {
1909
+ plyr.embed.pause();
1910
+ plyr.media.paused = true;
1911
+ };
1912
+ plyr.media.stop = function() {
1913
+ plyr.embed.seekTo(0);
1914
+ plyr.embed.pause();
1915
+ plyr.media.paused = true;
1916
+ };
1917
+
1918
+ plyr.media.paused = true;
1919
+ plyr.media.currentTime = 0;
1920
+
1921
+ plyr.embed.getDuration(function(value) {
1922
+ plyr.media.duration = value/1000;
1923
+
1924
+ // Update UI
1925
+ _embedReady();
1926
+ });
1927
+
1928
+ plyr.embed.getPosition(function(value) {
1929
+ plyr.media.currentTime = value;
1930
+
1931
+ // Trigger timeupdate
1932
+ _triggerEvent(plyr.media, 'timeupdate');
1933
+ });
1934
+
1935
+ plyr.embed.bind(window.SC.Widget.Events.PLAY, function() {
1936
+ plyr.media.paused = false;
1937
+ _triggerEvent(plyr.media, 'play');
1938
+ _triggerEvent(plyr.media, 'playing');
1939
+ });
1940
+
1941
+ plyr.embed.bind(window.SC.Widget.Events.PAUSE, function() {
1942
+ plyr.media.paused = true;
1943
+ _triggerEvent(plyr.media, 'pause');
1944
+ });
1945
+
1946
+ plyr.embed.bind(window.SC.Widget.Events.PLAY_PROGRESS, function(data) {
1947
+ plyr.media.seeking = false;
1948
+ plyr.media.currentTime = data.currentPosition/1000;
1949
+ _triggerEvent(plyr.media, 'timeupdate');
1950
+ });
1951
+
1952
+ plyr.embed.bind(window.SC.Widget.Events.LOAD_PROGRESS, function(data) {
1953
+ plyr.media.buffered = data.loadProgress;
1954
+ _triggerEvent(plyr.media, 'progress');
1955
+
1956
+ if (parseInt(data.loadProgress) === 1) {
1957
+ // Trigger event
1958
+ _triggerEvent(plyr.media, 'canplaythrough');
1959
+ }
1960
+ });
1961
+
1962
+ plyr.embed.bind(window.SC.Widget.Events.FINISH, function() {
1963
+ plyr.media.paused = true;
1964
+ _triggerEvent(plyr.media, 'ended');
1965
+ });
1966
+ });
1967
+ }
1968
+
1969
+ // Play media
1970
+ function _play() {
1971
+ if ('play' in plyr.media) {
1972
+ plyr.media.play();
1973
+ }
1974
+ }
1975
+
1976
+ // Pause media
1977
+ function _pause() {
1978
+ if ('pause' in plyr.media) {
1979
+ plyr.media.pause();
1980
+ }
1981
+ }
1982
+
1983
+ // Toggle playback
1984
+ function _togglePlay(toggle) {
1985
+ // True toggle
1986
+ if (!_is.boolean(toggle)) {
1987
+ toggle = plyr.media.paused;
1988
+ }
1989
+
1990
+ if (toggle) {
1991
+ _play();
1992
+ } else {
1993
+ _pause();
1994
+ }
1995
+
1996
+ return toggle;
1997
+ }
1998
+
1999
+ // Rewind
2000
+ function _rewind(seekTime) {
2001
+ // Use default if needed
2002
+ if (!_is.number(seekTime)) {
2003
+ seekTime = config.seekTime;
2004
+ }
2005
+ _seek(plyr.media.currentTime - seekTime);
2006
+ }
2007
+
2008
+ // Fast forward
2009
+ function _forward(seekTime) {
2010
+ // Use default if needed
2011
+ if (!_is.number(seekTime)) {
2012
+ seekTime = config.seekTime;
2013
+ }
2014
+ _seek(plyr.media.currentTime + seekTime);
2015
+ }
2016
+
2017
+ // Seek to time
2018
+ // The input parameter can be an event or a number
2019
+ function _seek(input) {
2020
+ var targetTime = 0,
2021
+ paused = plyr.media.paused,
2022
+ duration = _getDuration();
2023
+
2024
+ if (_is.number(input)) {
2025
+ targetTime = input;
2026
+ } else if (_is.object(input) && _inArray(['input', 'change'], input.type)) {
2027
+ // It's the seek slider
2028
+ // Seek to the selected time
2029
+ targetTime = ((input.target.value / input.target.max) * duration);
2030
+ }
2031
+
2032
+ // Normalise targetTime
2033
+ if (targetTime < 0) {
2034
+ targetTime = 0;
2035
+ } else if (targetTime > duration) {
2036
+ targetTime = duration;
2037
+ }
2038
+
2039
+ // Update seek range and progress
2040
+ _updateSeekDisplay(targetTime);
2041
+
2042
+ // Set the current time
2043
+ // Try/catch incase the media isn't set and we're calling seek() from source() and IE moans
2044
+ try {
2045
+ plyr.media.currentTime = targetTime.toFixed(4);
2046
+ }
2047
+ catch(e) {}
2048
+
2049
+ // Embeds
2050
+ if (_inArray(config.types.embed, plyr.type)) {
2051
+ switch(plyr.type) {
2052
+ case 'youtube':
2053
+ plyr.embed.seekTo(targetTime);
2054
+ break;
2055
+
2056
+ case 'vimeo':
2057
+ // Round to nearest second for vimeo
2058
+ plyr.embed.setCurrentTime(targetTime.toFixed(0));
2059
+ break;
2060
+
2061
+ case 'soundcloud':
2062
+ plyr.embed.seekTo(targetTime * 1000);
2063
+ break;
2064
+ }
2065
+
2066
+ if (paused) {
2067
+ _pause();
2068
+ }
2069
+
2070
+ // Trigger timeupdate
2071
+ _triggerEvent(plyr.media, 'timeupdate');
2072
+
2073
+ // Set seeking flag
2074
+ plyr.media.seeking = true;
2075
+
2076
+ // Trigger seeking
2077
+ _triggerEvent(plyr.media, 'seeking');
2078
+ }
2079
+
2080
+ // Logging
2081
+ _log('Seeking to ' + plyr.media.currentTime + ' seconds');
2082
+
2083
+ // Special handling for 'manual' captions
2084
+ _seekManualCaptions(targetTime);
2085
+ }
2086
+
2087
+ // Get the duration (or custom if set)
2088
+ function _getDuration() {
2089
+ // It should be a number, but parse it just incase
2090
+ var duration = parseInt(config.duration),
2091
+
2092
+ // True duration
2093
+ mediaDuration = 0;
2094
+
2095
+ // Only if duration available
2096
+ if (plyr.media.duration !== null && !isNaN(plyr.media.duration)) {
2097
+ mediaDuration = plyr.media.duration;
2098
+ }
2099
+
2100
+ // If custom duration is funky, use regular duration
2101
+ return (isNaN(duration) ? mediaDuration : duration);
2102
+ }
2103
+
2104
+ // Check playing state
2105
+ function _checkPlaying() {
2106
+ _toggleClass(plyr.container, config.classes.playing, !plyr.media.paused);
2107
+
2108
+ _toggleClass(plyr.container, config.classes.stopped, plyr.media.paused);
2109
+
2110
+ _toggleControls(plyr.media.paused);
2111
+ }
2112
+
2113
+ // Save scroll position
2114
+ function _saveScrollPosition() {
2115
+ scroll = {
2116
+ x: window.pageXOffset || 0,
2117
+ y: window.pageYOffset || 0
2118
+ };
2119
+ }
2120
+
2121
+ // Restore scroll position
2122
+ function _restoreScrollPosition() {
2123
+ window.scrollTo(scroll.x, scroll.y);
2124
+ }
2125
+
2126
+ // Toggle fullscreen
2127
+ function _toggleFullscreen(event) {
2128
+ // Check for native support
2129
+ var nativeSupport = fullscreen.supportsFullScreen;
2130
+
2131
+ if (nativeSupport) {
2132
+ // If it's a fullscreen change event, update the UI
2133
+ if (event && event.type === fullscreen.fullScreenEventName) {
2134
+ plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
2135
+ } else {
2136
+ // Else it's a user request to enter or exit
2137
+ if (!fullscreen.isFullScreen(plyr.container)) {
2138
+ // Save scroll position
2139
+ _saveScrollPosition();
2140
+
2141
+ // Request full screen
2142
+ fullscreen.requestFullScreen(plyr.container);
2143
+ } else {
2144
+ // Bail from fullscreen
2145
+ fullscreen.cancelFullScreen();
2146
+ }
2147
+
2148
+ // Check if we're actually full screen (it could fail)
2149
+ plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
2150
+
2151
+ return;
2152
+ }
2153
+ } else {
2154
+ // Otherwise, it's a simple toggle
2155
+ plyr.isFullscreen = !plyr.isFullscreen;
2156
+
2157
+ // Bind/unbind escape key
2158
+ document.body.style.overflow = plyr.isFullscreen ? 'hidden' : '';
2159
+ }
2160
+
2161
+ // Set class hook
2162
+ _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
2163
+
2164
+ // Trap focus
2165
+ _focusTrap(plyr.isFullscreen);
2166
+
2167
+ // Set button state
2168
+ if (plyr.buttons && plyr.buttons.fullscreen) {
2169
+ _toggleState(plyr.buttons.fullscreen, plyr.isFullscreen);
2170
+ }
2171
+
2172
+ // Trigger an event
2173
+ _triggerEvent(plyr.container, plyr.isFullscreen ? 'enterfullscreen' : 'exitfullscreen', true);
2174
+
2175
+ // Restore scroll position
2176
+ if (!plyr.isFullscreen && nativeSupport) {
2177
+ _restoreScrollPosition();
2178
+ }
2179
+ }
2180
+
2181
+ // Mute
2182
+ function _toggleMute(muted) {
2183
+ // If the method is called without parameter, toggle based on current value
2184
+ if (!_is.boolean(muted)) {
2185
+ muted = !plyr.media.muted;
2186
+ }
2187
+
2188
+ // Set button state
2189
+ _toggleState(plyr.buttons.mute, muted);
2190
+
2191
+ // Set mute on the player
2192
+ plyr.media.muted = muted;
2193
+
2194
+ // If volume is 0 after unmuting, set to default
2195
+ if (plyr.media.volume === 0) {
2196
+ _setVolume(config.volume);
2197
+ }
2198
+
2199
+ // Embeds
2200
+ if (_inArray(config.types.embed, plyr.type)) {
2201
+ // YouTube
2202
+ switch(plyr.type) {
2203
+ case 'youtube':
2204
+ plyr.embed[plyr.media.muted ? 'mute' : 'unMute']();
2205
+ break;
2206
+
2207
+ case 'vimeo':
2208
+ case 'soundcloud':
2209
+ plyr.embed.setVolume(plyr.media.muted ? 0 : parseFloat(config.volume / config.volumeMax));
2210
+ break;
2211
+ }
2212
+
2213
+ // Trigger volumechange for embeds
2214
+ _triggerEvent(plyr.media, 'volumechange');
2215
+ }
2216
+ }
2217
+
2218
+ // Set volume
2219
+ function _setVolume(volume) {
2220
+ var max = config.volumeMax,
2221
+ min = config.volumeMin;
2222
+
2223
+ // Load volume from storage if no value specified
2224
+ if (_is.undefined(volume)) {
2225
+ volume = plyr.storage.volume;
2226
+ }
2227
+
2228
+ // Use config if all else fails
2229
+ if (volume === null || isNaN(volume)) {
2230
+ volume = config.volume;
2231
+ }
2232
+
2233
+ // Maximum is volumeMax
2234
+ if (volume > max) {
2235
+ volume = max;
2236
+ }
2237
+ // Minimum is volumeMin
2238
+ if (volume < min) {
2239
+ volume = min;
2240
+ }
2241
+
2242
+ // Set the player volume
2243
+ plyr.media.volume = parseFloat(volume / max);
2244
+
2245
+ // Set the display
2246
+ if (plyr.volume.display) {
2247
+ plyr.volume.display.value = volume;
2248
+ }
2249
+
2250
+ // Embeds
2251
+ if (_inArray(config.types.embed, plyr.type)) {
2252
+ switch(plyr.type) {
2253
+ case 'youtube':
2254
+ plyr.embed.setVolume(plyr.media.volume * 100);
2255
+ break;
2256
+
2257
+ case 'vimeo':
2258
+ case 'soundcloud':
2259
+ plyr.embed.setVolume(plyr.media.volume);
2260
+ break;
2261
+ }
2262
+
2263
+ // Trigger volumechange for embeds
2264
+ _triggerEvent(plyr.media, 'volumechange');
2265
+ }
2266
+
2267
+ // Toggle muted state
2268
+ if (volume === 0) {
2269
+ plyr.media.muted = true;
2270
+ } else if (plyr.media.muted && volume > 0) {
2271
+ _toggleMute();
2272
+ }
2273
+ }
2274
+
2275
+ // Increase volume
2276
+ function _increaseVolume(step) {
2277
+ var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
2278
+
2279
+ if (!_is.number(step)) {
2280
+ step = config.volumeStep;
2281
+ }
2282
+
2283
+ _setVolume(volume + step);
2284
+ }
2285
+
2286
+ // Decrease volume
2287
+ function _decreaseVolume(step) {
2288
+ var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
2289
+
2290
+ if (!_is.number(step)) {
2291
+ step = config.volumeStep;
2292
+ }
2293
+
2294
+ _setVolume(volume - step);
2295
+ }
2296
+
2297
+ // Update volume UI and storage
2298
+ function _updateVolume() {
2299
+ // Get the current volume
2300
+ var volume = plyr.media.muted ? 0 : (plyr.media.volume * config.volumeMax);
2301
+
2302
+ // Update the <input type="range"> if present
2303
+ if (plyr.supported.full) {
2304
+ if (plyr.volume.input) {
2305
+ plyr.volume.input.value = volume;
2306
+ }
2307
+ if (plyr.volume.display) {
2308
+ plyr.volume.display.value = volume;
2309
+ }
2310
+ }
2311
+
2312
+ // Update the volume in storage
2313
+ _updateStorage({volume: volume});
2314
+
2315
+ // Toggle class if muted
2316
+ _toggleClass(plyr.container, config.classes.muted, (volume === 0));
2317
+
2318
+ // Update checkbox for mute state
2319
+ if (plyr.supported.full && plyr.buttons.mute) {
2320
+ _toggleState(plyr.buttons.mute, (volume === 0));
2321
+ }
2322
+ }
2323
+
2324
+ // Toggle captions
2325
+ function _toggleCaptions(show) {
2326
+ // If there's no full support, or there's no caption toggle
2327
+ if (!plyr.supported.full || !plyr.buttons.captions) {
2328
+ return;
2329
+ }
2330
+
2331
+ // If the method is called without parameter, toggle based on current value
2332
+ if (!_is.boolean(show)) {
2333
+ show = (plyr.container.className.indexOf(config.classes.captions.active) === -1);
2334
+ }
2335
+
2336
+ // Set global
2337
+ plyr.captionsEnabled = show;
2338
+
2339
+ // Toggle state
2340
+ _toggleState(plyr.buttons.captions, plyr.captionsEnabled);
2341
+
2342
+ // Add class hook
2343
+ _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
2344
+
2345
+ // Trigger an event
2346
+ _triggerEvent(plyr.container, plyr.captionsEnabled ? 'captionsenabled' : 'captionsdisabled', true);
2347
+
2348
+ // Save captions state to localStorage
2349
+ _updateStorage({captionsEnabled: plyr.captionsEnabled});
2350
+ }
2351
+
2352
+ // Check if media is loading
2353
+ function _checkLoading(event) {
2354
+ var loading = (event.type === 'waiting');
2355
+
2356
+ // Clear timer
2357
+ clearTimeout(timers.loading);
2358
+
2359
+ // Timer to prevent flicker when seeking
2360
+ timers.loading = setTimeout(function() {
2361
+ // Toggle container class hook
2362
+ _toggleClass(plyr.container, config.classes.loading, loading);
2363
+
2364
+ // Show controls if loading, hide if done
2365
+ _toggleControls(loading);
2366
+ }, (loading ? 250 : 0));
2367
+ }
2368
+
2369
+ // Update <progress> elements
2370
+ function _updateProgress(event) {
2371
+ if (!plyr.supported.full) {
2372
+ return;
2373
+ }
2374
+
2375
+ var progress = plyr.progress.played,
2376
+ value = 0,
2377
+ duration = _getDuration();
2378
+
2379
+ if (event) {
2380
+ switch (event.type) {
2381
+ // Video playing
2382
+ case 'timeupdate':
2383
+ case 'seeking':
2384
+ if (plyr.controls.pressed) {
2385
+ return;
2386
+ }
2387
+
2388
+ value = _getPercentage(plyr.media.currentTime, duration);
2389
+
2390
+ // Set seek range value only if it's a 'natural' time event
2391
+ if (event.type === 'timeupdate' && plyr.buttons.seek) {
2392
+ plyr.buttons.seek.value = value;
2393
+ }
2394
+
2395
+ break;
2396
+
2397
+ // Check buffer status
2398
+ case 'playing':
2399
+ case 'progress':
2400
+ progress = plyr.progress.buffer;
2401
+ value = (function() {
2402
+ var buffered = plyr.media.buffered;
2403
+
2404
+ if (buffered && buffered.length) {
2405
+ // HTML5
2406
+ return _getPercentage(buffered.end(0), duration);
2407
+ } else if (_is.number(buffered)) {
2408
+ // YouTube returns between 0 and 1
2409
+ return (buffered * 100);
2410
+ }
2411
+
2412
+ return 0;
2413
+ })();
2414
+
2415
+ break;
2416
+ }
2417
+ }
2418
+
2419
+ // Set values
2420
+ _setProgress(progress, value);
2421
+ }
2422
+
2423
+ // Set <progress> value
2424
+ function _setProgress(progress, value) {
2425
+ if (!plyr.supported.full) {
2426
+ return;
2427
+ }
2428
+
2429
+ // Default to 0
2430
+ if (_is.undefined(value)) {
2431
+ value = 0;
2432
+ }
2433
+ // Default to buffer or bail
2434
+ if (_is.undefined(progress)) {
2435
+ if (plyr.progress && plyr.progress.buffer) {
2436
+ progress = plyr.progress.buffer;
2437
+ } else {
2438
+ return;
2439
+ }
2440
+ }
2441
+
2442
+ // One progress element passed
2443
+ if (_is.htmlElement(progress)) {
2444
+ progress.value = value;
2445
+ } else if (progress) {
2446
+ // Object of progress + text element
2447
+ if (progress.bar) {
2448
+ progress.bar.value = value;
2449
+ }
2450
+ if (progress.text) {
2451
+ progress.text.innerHTML = value;
2452
+ }
2453
+ }
2454
+ }
2455
+
2456
+ // Update the displayed time
2457
+ function _updateTimeDisplay(time, element) {
2458
+ // Bail if there's no duration display
2459
+ if (!element) {
2460
+ return;
2461
+ }
2462
+
2463
+ // Fallback to 0
2464
+ if (isNaN(time)) {
2465
+ time = 0;
2466
+ }
2467
+
2468
+ plyr.secs = parseInt(time % 60);
2469
+ plyr.mins = parseInt((time / 60) % 60);
2470
+ plyr.hours = parseInt(((time / 60) / 60) % 60);
2471
+
2472
+ // Do we need to display hours?
2473
+ var displayHours = (parseInt(((_getDuration() / 60) / 60) % 60) > 0);
2474
+
2475
+ // Ensure it's two digits. For example, 03 rather than 3.
2476
+ plyr.secs = ('0' + plyr.secs).slice(-2);
2477
+ plyr.mins = ('0' + plyr.mins).slice(-2);
2478
+
2479
+ // Render
2480
+ element.innerHTML = (displayHours ? plyr.hours + ':' : '') + plyr.mins + ':' + plyr.secs;
2481
+ }
2482
+
2483
+ // Show the duration on metadataloaded
2484
+ function _displayDuration() {
2485
+ if (!plyr.supported.full) {
2486
+ return;
2487
+ }
2488
+
2489
+ // Determine duration
2490
+ var duration = _getDuration() || 0;
2491
+
2492
+ // If there's only one time display, display duration there
2493
+ if (!plyr.duration && config.displayDuration && plyr.media.paused) {
2494
+ _updateTimeDisplay(duration, plyr.currentTime);
2495
+ }
2496
+
2497
+ // If there's a duration element, update content
2498
+ if (plyr.duration) {
2499
+ _updateTimeDisplay(duration, plyr.duration);
2500
+ }
2501
+
2502
+ // Update the tooltip (if visible)
2503
+ _updateSeekTooltip();
2504
+ }
2505
+
2506
+ // Handle time change event
2507
+ function _timeUpdate(event) {
2508
+ // Duration
2509
+ _updateTimeDisplay(plyr.media.currentTime, plyr.currentTime);
2510
+
2511
+ // Ignore updates while seeking
2512
+ if (event && event.type === 'timeupdate' && plyr.media.seeking) {
2513
+ return;
2514
+ }
2515
+
2516
+ // Playing progress
2517
+ _updateProgress(event);
2518
+ }
2519
+
2520
+ // Update seek range and progress
2521
+ function _updateSeekDisplay(time) {
2522
+ // Default to 0
2523
+ if (!_is.number(time)) {
2524
+ time = 0;
2525
+ }
2526
+
2527
+ var duration = _getDuration(),
2528
+ value = _getPercentage(time, duration);
2529
+
2530
+ // Update progress
2531
+ if (plyr.progress && plyr.progress.played) {
2532
+ plyr.progress.played.value = value;
2533
+ }
2534
+
2535
+ // Update seek range input
2536
+ if (plyr.buttons && plyr.buttons.seek) {
2537
+ plyr.buttons.seek.value = value;
2538
+ }
2539
+ }
2540
+
2541
+ // Update hover tooltip for seeking
2542
+ function _updateSeekTooltip(event) {
2543
+ var duration = _getDuration();
2544
+
2545
+ // Bail if setting not true
2546
+ if (!config.tooltips.seek || !plyr.progress.container || duration === 0) {
2547
+ return;
2548
+ }
2549
+
2550
+ // Calculate percentage
2551
+ var clientRect = plyr.progress.container.getBoundingClientRect(),
2552
+ percent = 0,
2553
+ visible = config.classes.tooltip + '--visible';
2554
+
2555
+ // Determine percentage, if already visible
2556
+ if (!event) {
2557
+ if (_hasClass(plyr.progress.tooltip, visible)) {
2558
+ percent = plyr.progress.tooltip.style.left.replace('%', '');
2559
+ } else {
2560
+ return;
2561
+ }
2562
+ } else {
2563
+ percent = ((100 / clientRect.width) * (event.pageX - clientRect.left));
2564
+ }
2565
+
2566
+ // Set bounds
2567
+ if (percent < 0) {
2568
+ percent = 0;
2569
+ } else if (percent > 100) {
2570
+ percent = 100;
2571
+ }
2572
+
2573
+ // Display the time a click would seek to
2574
+ _updateTimeDisplay(((duration / 100) * percent), plyr.progress.tooltip);
2575
+
2576
+ // Set position
2577
+ plyr.progress.tooltip.style.left = percent + "%";
2578
+
2579
+ // Show/hide the tooltip
2580
+ // If the event is a moues in/out and percentage is inside bounds
2581
+ if (event && _inArray(['mouseenter', 'mouseleave'], event.type)) {
2582
+ _toggleClass(plyr.progress.tooltip, visible, (event.type === 'mouseenter'));
2583
+ }
2584
+ }
2585
+
2586
+ // Show the player controls in fullscreen mode
2587
+ function _toggleControls(toggle) {
2588
+ // Don't hide if config says not to, it's audio, or not ready or loading
2589
+ if (!config.hideControls || plyr.type === 'audio') {
2590
+ return;
2591
+ }
2592
+
2593
+ var delay = 0,
2594
+ isEnterFullscreen = false,
2595
+ show = toggle,
2596
+ loading = _hasClass(plyr.container, config.classes.loading);
2597
+
2598
+ // Default to false if no boolean
2599
+ if (!_is.boolean(toggle)) {
2600
+ if (toggle && toggle.type) {
2601
+ // Is the enter fullscreen event
2602
+ isEnterFullscreen = (toggle.type === 'enterfullscreen');
2603
+
2604
+ // Whether to show controls
2605
+ show = _inArray(['mousemove', 'touchstart', 'mouseenter', 'focus'], toggle.type);
2606
+
2607
+ // Delay hiding on move events
2608
+ if (_inArray(['mousemove', 'touchmove'], toggle.type)) {
2609
+ delay = 2000;
2610
+ }
2611
+
2612
+ // Delay a little more for keyboard users
2613
+ if (toggle.type === 'focus') {
2614
+ delay = 3000;
2615
+ }
2616
+ } else {
2617
+ show = _hasClass(plyr.container, config.classes.hideControls);
2618
+ }
2619
+ }
2620
+
2621
+ // Clear timer every movement
2622
+ window.clearTimeout(timers.hover);
2623
+
2624
+ // If the mouse is not over the controls, set a timeout to hide them
2625
+ if (show || plyr.media.paused || loading) {
2626
+ _toggleClass(plyr.container, config.classes.hideControls, false);
2627
+
2628
+ // Always show controls when paused or if touch
2629
+ if (plyr.media.paused || loading) {
2630
+ return;
2631
+ }
2632
+
2633
+ // Delay for hiding on touch
2634
+ if (plyr.browser.isTouch) {
2635
+ delay = 3000;
2636
+ }
2637
+ }
2638
+
2639
+ // If toggle is false or if we're playing (regardless of toggle),
2640
+ // then set the timer to hide the controls
2641
+ if (!show || !plyr.media.paused) {
2642
+ timers.hover = window.setTimeout(function() {
2643
+ // If the mouse is over the controls (and not entering fullscreen), bail
2644
+ if ((plyr.controls.pressed || plyr.controls.hover) && !isEnterFullscreen) {
2645
+ return;
2646
+ }
2647
+
2648
+ _toggleClass(plyr.container, config.classes.hideControls, true);
2649
+ }, delay);
2650
+ }
2651
+ }
2652
+
2653
+ // Add common function to retrieve media source
2654
+ function _source(source) {
2655
+ // If not null or undefined, parse it
2656
+ if (!_is.undefined(source)) {
2657
+ _updateSource(source);
2658
+ return;
2659
+ }
2660
+
2661
+ // Return the current source
2662
+ var url;
2663
+ switch(plyr.type) {
2664
+ case 'youtube':
2665
+ url = plyr.embed.getVideoUrl();
2666
+ break;
2667
+
2668
+ case 'vimeo':
2669
+ plyr.embed.getVideoUrl.then(function (value) {
2670
+ url = value;
2671
+ });
2672
+ break;
2673
+
2674
+ case 'soundcloud':
2675
+ plyr.embed.getCurrentSound(function(object) {
2676
+ url = object.permalink_url;
2677
+ });
2678
+ break;
2679
+
2680
+ default:
2681
+ url = plyr.media.currentSrc;
2682
+ break;
2683
+ }
2684
+
2685
+ return url || '';
2686
+ }
2687
+
2688
+ // Update source
2689
+ // Sources are not checked for support so be careful
2690
+ function _updateSource(source) {
2691
+ if (!_is.object(source) || !('sources' in source) || !source.sources.length) {
2692
+ _warn('Invalid source format');
2693
+ return;
2694
+ }
2695
+
2696
+ // Remove ready class hook
2697
+ _toggleClass(plyr.container, config.classes.ready, false);
2698
+
2699
+ // Pause playback
2700
+ _pause();
2701
+
2702
+ // Update seek range and progress
2703
+ _updateSeekDisplay();
2704
+
2705
+ // Reset buffer progress
2706
+ _setProgress();
2707
+
2708
+ // Cancel current network requests
2709
+ _cancelRequests();
2710
+
2711
+ // Setup new source
2712
+ function setup() {
2713
+ // Remove embed object
2714
+ plyr.embed = null;
2715
+
2716
+ // Remove the old media
2717
+ _remove(plyr.media);
2718
+
2719
+ // Remove video container
2720
+ if (plyr.type === 'video' && plyr.videoContainer) {
2721
+ _remove(plyr.videoContainer);
2722
+ }
2723
+
2724
+ // Reset class name
2725
+ if (plyr.container) {
2726
+ plyr.container.removeAttribute('class');
2727
+ }
2728
+
2729
+ // Set the type
2730
+ if ('type' in source) {
2731
+ plyr.type = source.type;
2732
+
2733
+ // Get child type for video (it might be an embed)
2734
+ if (plyr.type === 'video') {
2735
+ var firstSource = source.sources[0];
2736
+
2737
+ if ('type' in firstSource && _inArray(config.types.embed, firstSource.type)) {
2738
+ plyr.type = firstSource.type;
2739
+ }
2740
+ }
2741
+ }
2742
+
2743
+ // Check for support
2744
+ plyr.supported = supported(plyr.type);
2745
+
2746
+ // Create new markup
2747
+ switch(plyr.type) {
2748
+ case 'video':
2749
+ plyr.media = document.createElement('video');
2750
+ break;
2751
+
2752
+ case 'audio':
2753
+ plyr.media = document.createElement('audio');
2754
+ break;
2755
+
2756
+ case 'youtube':
2757
+ case 'vimeo':
2758
+ case 'soundcloud':
2759
+ plyr.media = document.createElement('div');
2760
+ plyr.embedId = source.sources[0].src;
2761
+ break;
2762
+ }
2763
+
2764
+ // Inject the new element
2765
+ _prependChild(plyr.container, plyr.media);
2766
+
2767
+ // Autoplay the new source?
2768
+ if (_is.boolean(source.autoplay)) {
2769
+ config.autoplay = source.autoplay;
2770
+ }
2771
+
2772
+ // Set attributes for audio and video
2773
+ if (_inArray(config.types.html5, plyr.type)) {
2774
+ if (config.crossorigin) {
2775
+ plyr.media.setAttribute('crossorigin', '');
2776
+ }
2777
+ if (config.autoplay) {
2778
+ plyr.media.setAttribute('autoplay', '');
2779
+ }
2780
+ if ('poster' in source) {
2781
+ plyr.media.setAttribute('poster', source.poster);
2782
+ }
2783
+ if (config.loop) {
2784
+ plyr.media.setAttribute('loop', '');
2785
+ }
2786
+ }
2787
+
2788
+ // Restore class hooks
2789
+ _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
2790
+ _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
2791
+ _toggleStyleHook();
2792
+
2793
+ // Set new sources for html5
2794
+ if (_inArray(config.types.html5, plyr.type)) {
2795
+ _insertChildElements('source', source.sources);
2796
+ }
2797
+
2798
+ // Set up from scratch
2799
+ _setupMedia();
2800
+
2801
+ // HTML5 stuff
2802
+ if (_inArray(config.types.html5, plyr.type)) {
2803
+ // Setup captions
2804
+ if ('tracks' in source) {
2805
+ _insertChildElements('track', source.tracks);
2806
+ }
2807
+
2808
+ // Load HTML5 sources
2809
+ plyr.media.load();
2810
+ }
2811
+
2812
+ // If HTML5 or embed but not fully supported, setupInterface and call ready now
2813
+ if (_inArray(config.types.html5, plyr.type) || (_inArray(config.types.embed, plyr.type) && !plyr.supported.full)) {
2814
+ // Setup interface
2815
+ _setupInterface();
2816
+
2817
+ // Call ready
2818
+ _ready();
2819
+ }
2820
+
2821
+ // Set aria title and iframe title
2822
+ config.title = source.title;
2823
+ _setTitle();
2824
+ }
2825
+
2826
+ // Destroy instance adn wait for callback
2827
+ // Vimeo throws a wobbly if you don't wait
2828
+ _destroy(setup, false);
2829
+ }
2830
+
2831
+ // Update poster
2832
+ function _updatePoster(source) {
2833
+ if (plyr.type === 'video') {
2834
+ plyr.media.setAttribute('poster', source);
2835
+ }
2836
+ }
2837
+
2838
+ // Listen for control events
2839
+ function _controlListeners() {
2840
+ // IE doesn't support input event, so we fallback to change
2841
+ var inputEvent = (plyr.browser.isIE ? 'change' : 'input');
2842
+
2843
+ // Click play/pause helper
2844
+ function togglePlay() {
2845
+ var play = _togglePlay();
2846
+
2847
+ // Determine which buttons
2848
+ var trigger = plyr.buttons[play ? 'play' : 'pause'],
2849
+ target = plyr.buttons[play ? 'pause' : 'play'];
2850
+
2851
+ // Get the last play button to account for the large play button
2852
+ if (target && target.length > 1) {
2853
+ target = target[target.length - 1];
2854
+ } else {
2855
+ target = target[0];
2856
+ }
2857
+
2858
+ // Setup focus and tab focus
2859
+ if (target) {
2860
+ var hadTabFocus = _hasClass(trigger, config.classes.tabFocus);
2861
+
2862
+ setTimeout(function() {
2863
+ target.focus();
2864
+
2865
+ if (hadTabFocus) {
2866
+ _toggleClass(trigger, config.classes.tabFocus, false);
2867
+ _toggleClass(target, config.classes.tabFocus, true);
2868
+ }
2869
+ }, 100);
2870
+ }
2871
+ }
2872
+
2873
+ // Get the focused element
2874
+ function getFocusElement() {
2875
+ var focused = document.activeElement;
2876
+
2877
+ if (!focused || focused === document.body) {
2878
+ focused = null;
2879
+ } else {
2880
+ focused = document.querySelector(':focus');
2881
+ }
2882
+
2883
+ return focused;
2884
+ }
2885
+
2886
+ // Get the key code for an event
2887
+ function getKeyCode(event) {
2888
+ return event.keyCode ? event.keyCode : event.which;
2889
+ }
2890
+
2891
+ // Detect tab focus
2892
+ function checkTabFocus(focused) {
2893
+ for (var button in plyr.buttons) {
2894
+ var element = plyr.buttons[button];
2895
+
2896
+ if (_is.nodeList(element)) {
2897
+ for (var i = 0; i < element.length; i++) {
2898
+ _toggleClass(element[i], config.classes.tabFocus, (element[i] === focused));
2899
+ }
2900
+ } else {
2901
+ _toggleClass(element, config.classes.tabFocus, (element === focused));
2902
+ }
2903
+ }
2904
+ }
2905
+
2906
+ // Keyboard shortcuts
2907
+ if (config.keyboardShorcuts.focused) {
2908
+ var last = null;
2909
+
2910
+ // Handle global presses
2911
+ if (config.keyboardShorcuts.global) {
2912
+ _on(window, 'keydown keyup', function(event) {
2913
+ var code = getKeyCode(event),
2914
+ focused = getFocusElement(),
2915
+ allowed = [48,49,50,51,52,53,54,56,57,75,77,70,67],
2916
+ count = get().length;
2917
+
2918
+ // Only handle global key press if there's only one player
2919
+ // and the key is in the allowed keys
2920
+ // and if the focused element is not editable (e.g. text input)
2921
+ // and any that accept key input http://webaim.org/techniques/keyboard/
2922
+ if (count === 1 && _inArray(allowed, code) && (!_is.htmlElement(focused) || !_matches(focused, config.selectors.editable))) {
2923
+ handleKey(event);
2924
+ }
2925
+ });
2926
+ }
2927
+
2928
+ // Handle presses on focused
2929
+ _on(plyr.container, 'keydown keyup', handleKey);
2930
+ }
2931
+
2932
+ function handleKey(event) {
2933
+ var code = getKeyCode(event),
2934
+ pressed = event.type === 'keydown',
2935
+ held = pressed && code === last;
2936
+
2937
+ // If the event is bubbled from the media element
2938
+ // Firefox doesn't get the keycode for whatever reason
2939
+ if (!_is.number(code)) {
2940
+ return;
2941
+ }
2942
+
2943
+ // Seek by the number keys
2944
+ function seekByKey() {
2945
+ // Get current duration
2946
+ var duration = plyr.media.duration;
2947
+
2948
+ // Bail if we have no duration set
2949
+ if (!_is.number(duration)) {
2950
+ return;
2951
+ }
2952
+
2953
+ // Divide the max duration into 10th's and times by the number value
2954
+ _seek((duration / 10) * (code - 48));
2955
+ }
2956
+
2957
+ // Handle the key on keydown
2958
+ // Reset on keyup
2959
+ if (pressed) {
2960
+ // Which keycodes should we prevent default
2961
+ var preventDefault = [48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67];
2962
+
2963
+ // If the code is found prevent default (e.g. prevent scrolling for arrows)
2964
+ if (_inArray(preventDefault, code)) {
2965
+ event.preventDefault();
2966
+ event.stopPropagation();
2967
+ }
2968
+
2969
+ switch(code) {
2970
+ // 0-9
2971
+ case 48:
2972
+ case 49:
2973
+ case 50:
2974
+ case 51:
2975
+ case 52:
2976
+ case 53:
2977
+ case 54:
2978
+ case 55:
2979
+ case 56:
2980
+ case 57: if (!held) { seekByKey(); } break;
2981
+ // Space and K key
2982
+ case 32:
2983
+ case 75: if (!held) { _togglePlay(); } break;
2984
+ // Arrow up
2985
+ case 38: _increaseVolume(); break;
2986
+ // Arrow down
2987
+ case 40: _decreaseVolume(); break;
2988
+ // M key
2989
+ case 77: if (!held) { _toggleMute() } break;
2990
+ // Arrow forward
2991
+ case 39: _forward(); break;
2992
+ // Arrow back
2993
+ case 37: _rewind(); break;
2994
+ // F key
2995
+ case 70: _toggleFullscreen(); break;
2996
+ // C key
2997
+ case 67: if (!held) { _toggleCaptions(); } break;
2998
+ }
2999
+
3000
+ // Escape is handle natively when in full screen
3001
+ // So we only need to worry about non native
3002
+ if (!fullscreen.supportsFullScreen && plyr.isFullscreen && code === 27) {
3003
+ _toggleFullscreen();
3004
+ }
3005
+
3006
+ // Store last code for next cycle
3007
+ last = code;
3008
+ } else {
3009
+ last = null;
3010
+ }
3011
+ }
3012
+
3013
+ // Focus/tab management
3014
+ _on(window, 'keyup', function(event) {
3015
+ var code = getKeyCode(event),
3016
+ focused = getFocusElement();
3017
+
3018
+ if (code === 9) {
3019
+ checkTabFocus(focused);
3020
+ }
3021
+ });
3022
+ _on(document.body, 'click', function() {
3023
+ _toggleClass(_getElement('.' + config.classes.tabFocus), config.classes.tabFocus, false);
3024
+ });
3025
+ for (var button in plyr.buttons) {
3026
+ var element = plyr.buttons[button];
3027
+
3028
+ _on(element, 'blur', function() {
3029
+ _toggleClass(element, 'tab-focus', false);
3030
+ });
3031
+ }
3032
+
3033
+ // Play
3034
+ _proxyListener(plyr.buttons.play, 'click', config.listeners.play, togglePlay);
3035
+
3036
+ // Pause
3037
+ _proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, togglePlay);
3038
+
3039
+ // Restart
3040
+ _proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek);
3041
+
3042
+ // Rewind
3043
+ _proxyListener(plyr.buttons.rewind, 'click', config.listeners.rewind, _rewind);
3044
+
3045
+ // Fast forward
3046
+ _proxyListener(plyr.buttons.forward, 'click', config.listeners.forward, _forward);
3047
+
3048
+ // Seek
3049
+ _proxyListener(plyr.buttons.seek, inputEvent, config.listeners.seek, _seek);
3050
+
3051
+ // Set volume
3052
+ _proxyListener(plyr.volume.input, inputEvent, config.listeners.volume, function() {
3053
+ _setVolume(plyr.volume.input.value);
3054
+ });
3055
+
3056
+ // Mute
3057
+ _proxyListener(plyr.buttons.mute, 'click', config.listeners.mute, _toggleMute);
3058
+
3059
+ // Fullscreen
3060
+ _proxyListener(plyr.buttons.fullscreen, 'click', config.listeners.fullscreen, _toggleFullscreen);
3061
+
3062
+ // Handle user exiting fullscreen by escaping etc
3063
+ if (fullscreen.supportsFullScreen) {
3064
+ _on(document, fullscreen.fullScreenEventName, _toggleFullscreen);
3065
+ }
3066
+
3067
+ // Captions
3068
+ _on(plyr.buttons.captions, 'click', _toggleCaptions);
3069
+
3070
+ // Seek tooltip
3071
+ _on(plyr.progress.container, 'mouseenter mouseleave mousemove', _updateSeekTooltip);
3072
+
3073
+ // Toggle controls visibility based on mouse movement
3074
+ if (config.hideControls) {
3075
+ // Toggle controls on mouse events and entering fullscreen
3076
+ _on(plyr.container, 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', _toggleControls);
3077
+
3078
+ // Watch for cursor over controls so they don't hide when trying to interact
3079
+ _on(plyr.controls, 'mouseenter mouseleave', function(event) {
3080
+ plyr.controls.hover = event.type === 'mouseenter';
3081
+ });
3082
+
3083
+ // Watch for cursor over controls so they don't hide when trying to interact
3084
+ _on(plyr.controls, 'mousedown mouseup touchstart touchend touchcancel', function(event) {
3085
+ plyr.controls.pressed = _inArray(['mousedown', 'touchstart'], event.type);
3086
+ });
3087
+
3088
+ // Focus in/out on controls
3089
+ _on(plyr.controls, 'focus blur', _toggleControls, true);
3090
+ }
3091
+
3092
+ // Adjust volume on scroll
3093
+ _on(plyr.volume.input, 'wheel', function(event) {
3094
+ event.preventDefault();
3095
+
3096
+ // Detect "natural" scroll - suppored on OS X Safari only
3097
+ // Other browsers on OS X will be inverted until support improves
3098
+ var inverted = event.webkitDirectionInvertedFromDevice,
3099
+ step = (config.volumeStep / 5);
3100
+
3101
+ // Scroll down (or up on natural) to decrease
3102
+ if (event.deltaY < 0 || event.deltaX > 0) {
3103
+ if (inverted) {
3104
+ _decreaseVolume(step);
3105
+ } else {
3106
+ _increaseVolume(step);
3107
+ }
3108
+ }
3109
+
3110
+ // Scroll up (or down on natural) to increase
3111
+ if (event.deltaY > 0 || event.deltaX < 0) {
3112
+ if (inverted) {
3113
+ _increaseVolume(step);
3114
+ } else {
3115
+ _decreaseVolume(step);
3116
+ }
3117
+ }
3118
+ });
3119
+ }
3120
+
3121
+ // Listen for media events
3122
+ function _mediaListeners() {
3123
+ // Time change on media
3124
+ _on(plyr.media, 'timeupdate seeking', _timeUpdate);
3125
+
3126
+ // Update manual captions
3127
+ _on(plyr.media, 'timeupdate', _seekManualCaptions);
3128
+
3129
+ // Display duration
3130
+ _on(plyr.media, 'durationchange loadedmetadata', _displayDuration);
3131
+
3132
+ // Handle the media finishing
3133
+ _on(plyr.media, 'ended', function() {
3134
+ // Show poster on end
3135
+ if (plyr.type === 'video' && config.showPosterOnEnd) {
3136
+ // Clear
3137
+ if (plyr.type === 'video') {
3138
+ _setCaption();
3139
+ }
3140
+
3141
+ // Restart
3142
+ _seek();
3143
+
3144
+ // Re-load media
3145
+ plyr.media.load();
3146
+ }
3147
+ });
3148
+
3149
+ // Check for buffer progress
3150
+ _on(plyr.media, 'progress playing', _updateProgress);
3151
+
3152
+ // Handle native mute
3153
+ _on(plyr.media, 'volumechange', _updateVolume);
3154
+
3155
+ // Handle native play/pause
3156
+ _on(plyr.media, 'play pause ended', _checkPlaying);
3157
+
3158
+ // Loading
3159
+ _on(plyr.media, 'waiting canplay seeked', _checkLoading);
3160
+
3161
+ // Click video
3162
+ if (config.clickToPlay && plyr.type !== 'audio') {
3163
+ // Re-fetch the wrapper
3164
+ var wrapper = _getElement('.' + config.classes.videoWrapper);
3165
+
3166
+ // Bail if there's no wrapper (this should never happen)
3167
+ if (!wrapper) {
3168
+ return;
3169
+ }
3170
+
3171
+ // Set cursor
3172
+ wrapper.style.cursor = "pointer";
3173
+
3174
+ // On click play, pause ore restart
3175
+ _on(wrapper, 'click', function() {
3176
+ // Touch devices will just show controls (if we're hiding controls)
3177
+ if (config.hideControls && plyr.browser.isTouch && !plyr.media.paused) {
3178
+ return;
3179
+ }
3180
+
3181
+ if (plyr.media.paused) {
3182
+ _play();
3183
+ } else if (plyr.media.ended) {
3184
+ _seek();
3185
+ _play();
3186
+ } else {
3187
+ _pause();
3188
+ }
3189
+ });
3190
+ }
3191
+
3192
+ // Disable right click
3193
+ if (config.disableContextMenu) {
3194
+ _on(plyr.media, 'contextmenu', function(event) { event.preventDefault(); });
3195
+ }
3196
+
3197
+ // Proxy events to container
3198
+ // Bubble up key events for Edge
3199
+ _on(plyr.media, config.events.concat(['keyup', 'keydown']).join(' '), function(event) {
3200
+ _triggerEvent(plyr.container, event.type, true);
3201
+ });
3202
+ }
3203
+
3204
+ // Cancel current network requests
3205
+ // See https://github.com/Selz/plyr/issues/174
3206
+ function _cancelRequests() {
3207
+ if (!_inArray(config.types.html5, plyr.type)) {
3208
+ return;
3209
+ }
3210
+
3211
+ // Remove child sources
3212
+ var sources = plyr.media.querySelectorAll('source');
3213
+ for (var i = 0; i < sources.length; i++) {
3214
+ _remove(sources[i]);
3215
+ }
3216
+
3217
+ // Set blank video src attribute
3218
+ // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
3219
+ // Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
3220
+ plyr.media.setAttribute('src', 'https://cdn.selz.com/plyr/blank.mp4');
3221
+
3222
+ // Load the new empty source
3223
+ // This will cancel existing requests
3224
+ // See https://github.com/Selz/plyr/issues/174
3225
+ plyr.media.load();
3226
+
3227
+ // Debugging
3228
+ _log('Cancelled network requests');
3229
+ }
3230
+
3231
+ // Destroy an instance
3232
+ // Event listeners are removed when elements are removed
3233
+ // http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
3234
+ function _destroy(callback, restore) {
3235
+ // Bail if the element is not initialized
3236
+ if (!plyr.init) {
3237
+ return null;
3238
+ }
3239
+
3240
+ // Type specific stuff
3241
+ switch (plyr.type) {
3242
+ case 'youtube':
3243
+ // Clear timers
3244
+ window.clearInterval(timers.buffering);
3245
+ window.clearInterval(timers.playing);
3246
+
3247
+ // Destroy YouTube API
3248
+ plyr.embed.destroy();
3249
+
3250
+ // Clean up
3251
+ cleanUp();
3252
+
3253
+ break;
3254
+
3255
+ case 'vimeo':
3256
+ // Destroy Vimeo API
3257
+ // then clean up (wait, to prevent postmessage errors)
3258
+ plyr.embed.unload().then(cleanUp);
3259
+
3260
+ // Vimeo does not always return
3261
+ timers.cleanUp = window.setTimeout(cleanUp, 200);
3262
+
3263
+ break;
3264
+
3265
+ case 'video':
3266
+ case 'audio':
3267
+ // Restore native video controls
3268
+ _toggleNativeControls(true);
3269
+
3270
+ // Clean up
3271
+ cleanUp();
3272
+
3273
+ break;
3274
+ }
3275
+
3276
+ function cleanUp() {
3277
+ clearTimeout(timers.cleanUp);
3278
+
3279
+ // Default to restore original element
3280
+ if (!_is.boolean(restore)) {
3281
+ restore = true;
3282
+ }
3283
+
3284
+ // Callback
3285
+ if (_is.function(callback)) {
3286
+ callback.call(original);
3287
+ }
3288
+
3289
+ // Bail if we don't need to restore the original element
3290
+ if (!restore) {
3291
+ return;
3292
+ }
3293
+
3294
+ // Remove init flag
3295
+ plyr.init = false;
3296
+
3297
+ // Replace the container with the original element provided
3298
+ plyr.container.parentNode.replaceChild(original, plyr.container);
3299
+
3300
+ // Allow overflow (set on fullscreen)
3301
+ document.body.style.overflow = '';
3302
+
3303
+ // Event
3304
+ _triggerEvent(original, 'destroyed', true);
3305
+ }
3306
+ }
3307
+
3308
+ // Setup a player
3309
+ function _init() {
3310
+ // Bail if the element is initialized
3311
+ if (plyr.init) {
3312
+ return null;
3313
+ }
3314
+
3315
+ // Setup the fullscreen api
3316
+ fullscreen = _fullscreen();
3317
+
3318
+ // Sniff out the browser
3319
+ plyr.browser = _browserSniff();
3320
+
3321
+ // Bail if nothing to setup
3322
+ if (!_is.htmlElement(plyr.media)) {
3323
+ return;
3324
+ }
3325
+
3326
+ // Load saved settings from localStorage
3327
+ _setupStorage();
3328
+
3329
+ // Set media type based on tag or data attribute
3330
+ // Supported: video, audio, vimeo, youtube
3331
+ var tagName = media.tagName.toLowerCase();
3332
+ if (tagName === 'div') {
3333
+ plyr.type = media.getAttribute('data-type');
3334
+ plyr.embedId = media.getAttribute('data-video-id');
3335
+
3336
+ // Clean up
3337
+ media.removeAttribute('data-type');
3338
+ media.removeAttribute('data-video-id');
3339
+ } else {
3340
+ plyr.type = tagName;
3341
+ config.crossorigin = (media.getAttribute('crossorigin') !== null);
3342
+ config.autoplay = (config.autoplay || (media.getAttribute('autoplay') !== null));
3343
+ config.loop = (config.loop || (media.getAttribute('loop') !== null));
3344
+ }
3345
+
3346
+ // Check for support
3347
+ plyr.supported = supported(plyr.type);
3348
+
3349
+ // If no native support, bail
3350
+ if (!plyr.supported.basic) {
3351
+ return;
3352
+ }
3353
+
3354
+ // Wrap media
3355
+ plyr.container = _wrap(media, document.createElement('div'));
3356
+
3357
+ // Allow focus to be captured
3358
+ plyr.container.setAttribute('tabindex', 0);
3359
+
3360
+ // Add style hook
3361
+ _toggleStyleHook();
3362
+
3363
+ // Debug info
3364
+ _log('' + plyr.browser.name + ' ' + plyr.browser.version);
3365
+
3366
+ // Setup media
3367
+ _setupMedia();
3368
+
3369
+ // Setup interface
3370
+ // If embed but not fully supported, setupInterface (to avoid flash of controls) and call ready now
3371
+ if (_inArray(config.types.html5, plyr.type) || (_inArray(config.types.embed, plyr.type) && !plyr.supported.full)) {
3372
+ // Setup UI
3373
+ _setupInterface();
3374
+
3375
+ // Call ready
3376
+ _ready();
3377
+
3378
+ // Set title on button and frame
3379
+ _setTitle();
3380
+ }
3381
+
3382
+ // Successful setup
3383
+ plyr.init = true;
3384
+ }
3385
+
3386
+ // Setup the UI
3387
+ function _setupInterface() {
3388
+ // Don't setup interface if no support
3389
+ if (!plyr.supported.full) {
3390
+ _warn('Basic support only', plyr.type);
3391
+
3392
+ // Remove controls
3393
+ _remove(_getElement(config.selectors.controls.wrapper));
3394
+
3395
+ // Remove large play
3396
+ _remove(_getElement(config.selectors.buttons.play));
3397
+
3398
+ // Restore native controls
3399
+ _toggleNativeControls(true);
3400
+
3401
+ // Bail
3402
+ return;
3403
+ }
3404
+
3405
+ // Inject custom controls if not present
3406
+ var controlsMissing = !_getElements(config.selectors.controls.wrapper).length;
3407
+ if (controlsMissing) {
3408
+ // Inject custom controls
3409
+ _injectControls();
3410
+ }
3411
+
3412
+ // Find the elements
3413
+ if (!_findElements()) {
3414
+ return;
3415
+ }
3416
+
3417
+ // If the controls are injected, re-bind listeners for controls
3418
+ if (controlsMissing) {
3419
+ _controlListeners();
3420
+ }
3421
+
3422
+ // Media element listeners
3423
+ _mediaListeners();
3424
+
3425
+ // Remove native controls
3426
+ _toggleNativeControls();
3427
+
3428
+ // Setup fullscreen
3429
+ _setupFullscreen();
3430
+
3431
+ // Captions
3432
+ _setupCaptions();
3433
+
3434
+ // Set volume
3435
+ _setVolume();
3436
+ _updateVolume();
3437
+
3438
+ // Reset time display
3439
+ _timeUpdate();
3440
+
3441
+ // Update the UI
3442
+ _checkPlaying();
3443
+ }
3444
+
3445
+ api = {
3446
+ getOriginal: function() { return original; },
3447
+ getContainer: function() { return plyr.container },
3448
+ getEmbed: function() { return plyr.embed; },
3449
+ getMedia: function() { return plyr.media; },
3450
+ getType: function() { return plyr.type; },
3451
+ getDuration: _getDuration,
3452
+ getCurrentTime: function() { return plyr.media.currentTime; },
3453
+ getVolume: function() { return plyr.media.volume; },
3454
+ isMuted: function() { return plyr.media.muted; },
3455
+ isReady: function() { return _hasClass(plyr.container, config.classes.ready); },
3456
+ isLoading: function() { return _hasClass(plyr.container, config.classes.loading); },
3457
+ isPaused: function() { return plyr.media.paused; },
3458
+ on: function(event, callback) { _on(plyr.container, event, callback); return this; },
3459
+ play: _play,
3460
+ pause: _pause,
3461
+ stop: function() { _pause(); _seek(); },
3462
+ restart: _seek,
3463
+ rewind: _rewind,
3464
+ forward: _forward,
3465
+ seek: _seek,
3466
+ source: _source,
3467
+ poster: _updatePoster,
3468
+ setVolume: _setVolume,
3469
+ togglePlay: _togglePlay,
3470
+ toggleMute: _toggleMute,
3471
+ toggleCaptions: _toggleCaptions,
3472
+ toggleFullscreen: _toggleFullscreen,
3473
+ toggleControls: _toggleControls,
3474
+ isFullscreen: function() { return plyr.isFullscreen || false; },
3475
+ support: function(mimeType) { return _supportMime(plyr, mimeType); },
3476
+ destroy: _destroy
3477
+ };
3478
+
3479
+ // Everything done
3480
+ function _ready() {
3481
+ // Ready event at end of execution stack
3482
+ window.setTimeout(function() {
3483
+ _triggerEvent(plyr.media, 'ready');
3484
+ }, 0);
3485
+
3486
+ // Set class hook on media element
3487
+ _toggleClass(plyr.media, defaults.classes.setup, true);
3488
+
3489
+ // Set container class for ready
3490
+ _toggleClass(plyr.container, config.classes.ready, true);
3491
+
3492
+ // Store a refernce to instance
3493
+ plyr.media.plyr = api;
3494
+
3495
+ // Autoplay
3496
+ if (config.autoplay) {
3497
+ _play();
3498
+ }
3499
+ }
3500
+
3501
+ // Initialize instance
3502
+ _init();
3503
+
3504
+ // If init failed, return null
3505
+ if (!plyr.init) {
3506
+ return null;
3507
+ }
3508
+
3509
+ return api;
3510
+ }
3511
+
3512
+ // Load a sprite
3513
+ function loadSprite(url, id) {
3514
+ var x = new XMLHttpRequest();
3515
+
3516
+ // If the id is set and sprite exists, bail
3517
+ if (_is.string(id) && _is.htmlElement(document.querySelector('#' + id))) {
3518
+ return;
3519
+ }
3520
+
3521
+ // Create placeholder (to prevent loading twice)
3522
+ var container = document.createElement('div');
3523
+ container.setAttribute('hidden', '');
3524
+ if (_is.string(id)) {
3525
+ container.setAttribute('id', id);
3526
+ }
3527
+ document.body.insertBefore(container, document.body.childNodes[0]);
3528
+
3529
+ // Check for CORS support
3530
+ if ('withCredentials' in x) {
3531
+ x.open('GET', url, true);
3532
+ } else {
3533
+ return;
3534
+ }
3535
+
3536
+ // Inject hidden div with sprite on load
3537
+ x.onload = function() {
3538
+ container.innerHTML = x.responseText;
3539
+ }
3540
+
3541
+ x.send();
3542
+ }
3543
+
3544
+ // Check for support
3545
+ function supported(type) {
3546
+ var browser = _browserSniff(),
3547
+ isOldIE = (browser.isIE && browser.version <= 9),
3548
+ isIos = browser.isIos,
3549
+ isIphone = browser.isIphone,
3550
+ audioSupport = !!document.createElement('audio').canPlayType,
3551
+ videoSupport = !!document.createElement('video').canPlayType,
3552
+ basic = false,
3553
+ full = false;
3554
+
3555
+ switch (type) {
3556
+ case 'video':
3557
+ basic = videoSupport;
3558
+ full = (basic && (!isOldIE && !isIphone));
3559
+ break;
3560
+
3561
+ case 'audio':
3562
+ basic = audioSupport;
3563
+ full = (basic && !isOldIE);
3564
+ break;
3565
+
3566
+ // Vimeo does not seem to be supported on iOS via API
3567
+ // Issue raised https://github.com/vimeo/player.js/issues/87
3568
+ case 'vimeo':
3569
+ basic = true;
3570
+ full = (!isOldIE && !isIos);
3571
+ break;
3572
+
3573
+ case 'youtube':
3574
+ basic = true;
3575
+ full = (!isOldIE && !isIos);
3576
+
3577
+ // YouTube seems to work on iOS 10+ on iPad
3578
+ if (isIos && !isIphone && browser.version >= 10) {
3579
+ full = true;
3580
+ }
3581
+
3582
+ break;
3583
+
3584
+ case 'soundcloud':
3585
+ basic = true;
3586
+ full = (!isOldIE && !isIphone);
3587
+ break;
3588
+
3589
+ default:
3590
+ basic = (audioSupport && videoSupport);
3591
+ full = (basic && !isOldIE);
3592
+ }
3593
+
3594
+ return {
3595
+ basic: basic,
3596
+ full: full
3597
+ };
3598
+ }
3599
+
3600
+ // Setup function
3601
+ function setup(targets, options) {
3602
+ // Get the players
3603
+ var players = [],
3604
+ instances = [],
3605
+ selector = [defaults.selectors.html5, defaults.selectors.embed].join(',');
3606
+
3607
+ // Select the elements
3608
+ if (_is.string(targets)) {
3609
+ // String selector passed
3610
+ targets = document.querySelectorAll(targets);
3611
+ } else if (_is.htmlElement(targets)) {
3612
+ // Single HTMLElement passed
3613
+ targets = [targets];
3614
+ } else if (!_is.nodeList(targets) && !_is.array(targets) && !_is.string(targets)) {
3615
+ // No selector passed, possibly options as first argument
3616
+ // If options are the first argument
3617
+ if (_is.undefined(options) && _is.object(targets)) {
3618
+ options = targets;
3619
+ }
3620
+
3621
+ // Use default selector
3622
+ targets = document.querySelectorAll(selector);
3623
+ }
3624
+
3625
+ // Convert NodeList to array
3626
+ if (_is.nodeList(targets)) {
3627
+ targets = Array.prototype.slice.call(targets);
3628
+ }
3629
+
3630
+ // Bail if disabled or no basic support
3631
+ // You may want to disable certain UAs etc
3632
+ if (!supported().basic || !targets.length) {
3633
+ return false;
3634
+ }
3635
+
3636
+ // Add to container list
3637
+ function add(target, media) {
3638
+ if (!_hasClass(media, defaults.classes.hook)) {
3639
+ players.push({
3640
+ // Always wrap in a <div> for styling
3641
+ //container: _wrap(media, document.createElement('div')),
3642
+ // Could be a container or the media itself
3643
+ target: target,
3644
+ // This should be the <video>, <audio> or <div> (YouTube/Vimeo)
3645
+ media: media
3646
+ });
3647
+ }
3648
+ }
3649
+
3650
+ // Check if the targets have multiple media elements
3651
+ for (var i = 0; i < targets.length; i++) {
3652
+ var target = targets[i];
3653
+
3654
+ // Get children
3655
+ var children = target.querySelectorAll(selector);
3656
+
3657
+ // If there's more than one media element child, wrap them
3658
+ if (children.length) {
3659
+ for (var x = 0; x < children.length; x++) {
3660
+ add(target, children[x]);
3661
+ }
3662
+ } else if (_matches(target, selector)) {
3663
+ // Target is media element
3664
+ add(target, target);
3665
+ }
3666
+ }
3667
+
3668
+ // Create a player instance for each element
3669
+ players.forEach(function(player) {
3670
+ var element = player.target,
3671
+ media = player.media,
3672
+ match = false;
3673
+
3674
+ // The target element can also be the media element
3675
+ if (media === element) {
3676
+ match = true;
3677
+ }
3678
+
3679
+ // Setup a player instance and add to the element
3680
+ // Create instance-specific config
3681
+ var data = {};
3682
+
3683
+ // Try parsing data attribute config
3684
+ try { data = JSON.parse(element.getAttribute('data-plyr')); }
3685
+ catch(e) { }
3686
+
3687
+ var config = _extend({}, defaults, options, data);
3688
+
3689
+ // Bail if not enabled
3690
+ if (!config.enabled) {
3691
+ return null;
3692
+ }
3693
+
3694
+ // Create new instance
3695
+ var instance = new Plyr(media, config);
3696
+
3697
+ // Go to next if setup failed
3698
+ if (!_is.object(instance)) {
3699
+ return;
3700
+ }
3701
+
3702
+ // Listen for events if debugging
3703
+ if (config.debug) {
3704
+ var events = config.events.concat(['setup', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']);
3705
+
3706
+ _on(instance.getContainer(), events.join(' '), function(event) {
3707
+ console.log([config.logPrefix, 'event:', event.type].join(' '), event.detail.plyr);
3708
+ });
3709
+ }
3710
+
3711
+ // Callback
3712
+ _event(instance.getContainer(), 'setup', true, {
3713
+ plyr: instance
3714
+ });
3715
+
3716
+ // Add to return array even if it's already setup
3717
+ instances.push(instance);
3718
+ });
3719
+
3720
+ return instances;
3721
+ }
3722
+
3723
+ // Get all instances within a provided container
3724
+ function get(container) {
3725
+ if (_is.string(container)) {
3726
+ // Get selector if string passed
3727
+ container = document.querySelector(container);
3728
+ } else if (_is.undefined(container)) {
3729
+ // Use body by default to get all on page
3730
+ container = document.body;
3731
+ }
3732
+
3733
+ // If we have a HTML element
3734
+ if (_is.htmlElement(container)) {
3735
+ var elements = container.querySelectorAll('.' + defaults.classes.setup),
3736
+ instances = [];
3737
+
3738
+ Array.prototype.slice.call(elements).forEach(function(element) {
3739
+ if (_is.object(element.plyr)) {
3740
+ instances.push(element.plyr);
3741
+ }
3742
+ });
3743
+
3744
+ return instances;
3745
+ }
3746
+
3747
+ return [];
3748
+ }
3749
+
3750
+ return {
3751
+ setup: setup,
3752
+ supported: supported,
3753
+ loadSprite: loadSprite,
3754
+ get: get
3755
+ };
3756
+ }));
3757
+
3758
+ // Custom event polyfill
3759
+ // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
3760
+ (function () {
3761
+ if (typeof window.CustomEvent === 'function') {
3762
+ return;
3763
+ }
3764
+
3765
+ function CustomEvent(event, params) {
3766
+ params = params || { bubbles: false, cancelable: false, detail: undefined };
3767
+ var evt = document.createEvent('CustomEvent');
3768
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
3769
+ return evt;
3770
+ }
3771
+
3772
+ CustomEvent.prototype = window.Event.prototype;
3773
+
3774
+ window.CustomEvent = CustomEvent;
3775
+ })();