plyr-rails 2.0.11 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3a6d134588f8f50c366add75e88f20961ec2341
4
- data.tar.gz: 5e54eec74f7d3ad368982cf2ec3ef568e0808d08
3
+ metadata.gz: 0b68c85d16f138eb61ed634f6d49e0559963627c
4
+ data.tar.gz: c091f2d0729a0c7acf9c01cb063e411902746482
5
5
  SHA512:
6
- metadata.gz: 5e55ef225230fca754950a197fb72b7c5e6af4cbda5d8c331eee9d47b17b77f54a6ea39a7abf7ac8c00c27c2fb89de160a2be58a2f9f0e02bf068e5096ab562d
7
- data.tar.gz: 0df5c6e34f743755c087012e1348948bf08c1286a45da455eb1fcc5f22578116e7acfaff0ff655ec437e8243411e5425bd55203aaf8028eb91630b2f19b754cf
6
+ metadata.gz: 1893b4c943ce6b0f37bfa8cadc2c0bb3a0e8135135b1b53a07aca3010cff0c18d9b74f3efec897f171c48ceb75c36b170818cecea0fbce18befc0823d82a5d80
7
+ data.tar.gz: d91e46ac89e5b08bd5cff73c014383fa5afff009a1a149b5c724554588ea4f4ed15342b3071e159435c5a354e8736d53054cd50a639fda8a8cf99369ab5790b2
@@ -4,10 +4,7 @@ module Plyr
4
4
  module Rails
5
5
  class Engine < ::Rails::Engine
6
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 )
7
+ app.config.assets.paths += %w( sprite )
11
8
 
12
9
  app.config.assets.precompile += %w( plyr-captions-off.svg )
13
10
  app.config.assets.precompile += %w( plyr-captions-on.svg )
@@ -1,5 +1,5 @@
1
1
  module Plyr
2
2
  module Rails
3
- VERSION = "2.0.11"
3
+ VERSION = "2.0.12"
4
4
  end
5
5
  end
@@ -1,3775 +1,2 @@
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
- })();
1
+ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t(e,document):"function"==typeof define&&define.amd?define([],function(){return t(e,document)}):e.plyr=t(e,document)}("undefined"!=typeof window?window:this,function(e,t){"use strict";function n(){var e,n,r,a=navigator.userAgent,s=navigator.appName,o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10),l=!1,u=!1,c=!1,d=!1;return navigator.appVersion.indexOf("Windows NT")!==-1&&navigator.appVersion.indexOf("rv:11")!==-1?(l=!0,s="IE",o="11"):(n=a.indexOf("MSIE"))!==-1?(l=!0,s="IE",o=a.substring(n+5)):(n=a.indexOf("Chrome"))!==-1?(c=!0,s="Chrome",o=a.substring(n+7)):(n=a.indexOf("Safari"))!==-1?(d=!0,s="Safari",o=a.substring(n+7),(n=a.indexOf("Version"))!==-1&&(o=a.substring(n+8))):(n=a.indexOf("Firefox"))!==-1?(u=!0,s="Firefox",o=a.substring(n+8)):(e=a.lastIndexOf(" ")+1)<(n=a.lastIndexOf("/"))&&(s=a.substring(e,n),o=a.substring(n+1),s.toLowerCase()===s.toUpperCase()&&(s=navigator.appName)),(r=o.indexOf(";"))!==-1&&(o=o.substring(0,r)),(r=o.indexOf(" "))!==-1&&(o=o.substring(0,r)),i=parseInt(""+o,10),isNaN(i)&&(o=""+parseFloat(navigator.appVersion),i=parseInt(navigator.appVersion,10)),{name:s,version:i,isIE:l,isFirefox:u,isChrome:c,isSafari:d,isIos:/(iPad|iPhone|iPod)/g.test(navigator.platform),isIphone:/(iPhone|iPod)/g.test(navigator.userAgent),isTouch:"ontouchstart"in t.documentElement}}function r(e,t){var n=e.media;if("video"===e.type)switch(t){case"video/webm":return!(!n.canPlayType||!n.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/,""));case"video/mp4":return!(!n.canPlayType||!n.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/,""));case"video/ogg":return!(!n.canPlayType||!n.canPlayType('video/ogg; codecs="theora"').replace(/no/,""))}else if("audio"===e.type)switch(t){case"audio/mpeg":return!(!n.canPlayType||!n.canPlayType("audio/mpeg;").replace(/no/,""));case"audio/ogg":return!(!n.canPlayType||!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/,""));case"audio/wav":return!(!n.canPlayType||!n.canPlayType('audio/wav; codecs="1"').replace(/no/,""))}return!1}function a(e){if(!t.querySelectorAll('script[src="'+e+'"]').length){var n=t.createElement("script");n.src=e;var r=t.getElementsByTagName("script")[0];r.parentNode.insertBefore(n,r)}}function s(e,t){return Array.prototype.indexOf&&e.indexOf(t)!==-1}function o(e,t,n){return e.replace(new RegExp(t.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g,"\\$1"),"g"),n)}function i(e,t){e.length||(e=[e]);for(var n=e.length-1;n>=0;n--){var r=n>0?t.cloneNode(!0):t,a=e[n],s=a.parentNode,o=a.nextSibling;return r.appendChild(a),o?s.insertBefore(r,o):s.appendChild(r),r}}function l(e){e&&e.parentNode.removeChild(e)}function u(e,t){e.insertBefore(t,e.firstChild)}function c(e,t){for(var n in t)e.setAttribute(n,O.boolean(t[n])&&t[n]?"":t[n])}function d(e,n,r){var a=t.createElement(e);c(a,r),u(n,a)}function p(e){return e.replace(".","")}function m(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var r=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=r+(n?" "+t:"")}}function f(e,t){return!!e&&(e.classList?e.classList.contains(t):new RegExp("(\\s|^)"+t+"(\\s|$)").test(e.className))}function y(e,n){var r=Element.prototype,a=r.matches||r.webkitMatchesSelector||r.mozMatchesSelector||r.msMatchesSelector||function(e){return[].indexOf.call(t.querySelectorAll(e),this)!==-1};return a.call(e,n)}function b(e,t,n,r,a){g(e,t,function(t){n&&n.apply(e,[t]),r.apply(e,[t])},a)}function v(e,t,n,r,a){var s=t.split(" ");if(O.boolean(a)||(a=!1),e instanceof NodeList)for(var o=0;o<e.length;o++)e[o]instanceof Node&&v(e[o],arguments[1],arguments[2],arguments[3]);else for(var i=0;i<s.length;i++)e[r?"addEventListener":"removeEventListener"](s[i],n,a)}function g(e,t,n,r){e&&v(e,t,n,!0,r)}function h(e,t,n,r){if(e&&t){O.boolean(n)||(n=!1);var a=new CustomEvent(t,{bubbles:n,detail:r});e.dispatchEvent(a)}}function k(e,t){if(e)return t=O.boolean(t)?t:!e.getAttribute("aria-pressed"),e.setAttribute("aria-pressed",t),t}function w(e,t){return 0===e||0===t||isNaN(e)||isNaN(t)?0:(e/t*100).toFixed(2)}function x(){var e=arguments;if(e.length){if(1===e.length)return e[0];for(var t=Array.prototype.shift.call(e),n=e.length,r=0;r<n;r++){var a=e[r];for(var s in a)a[s]&&a[s].constructor&&a[s].constructor===Object?(t[s]=t[s]||{},x(t[s],a[s])):t[s]=a[s]}return t}}function T(e){var t=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;return e.match(t)?RegExp.$2:e}function S(e){var t=/^.*(vimeo.com\/|video\/)(\d+).*/;return e.match(t)?RegExp.$2:e}function _(){var e={supportsFullScreen:!1,isFullScreen:function(){return!1},requestFullScreen:function(){},cancelFullScreen:function(){},fullScreenEventName:"",element:null,prefix:""},n="webkit o moz ms khtml".split(" ");if(O.undefined(t.cancelFullScreen))for(var r=0,a=n.length;r<a;r++){if(e.prefix=n[r],!O.undefined(t[e.prefix+"CancelFullScreen"])){e.supportsFullScreen=!0;break}if(!O.undefined(t.msExitFullscreen)&&t.msFullscreenEnabled){e.prefix="ms",e.supportsFullScreen=!0;break}}else e.supportsFullScreen=!0;return e.supportsFullScreen&&(e.fullScreenEventName="ms"===e.prefix?"MSFullscreenChange":e.prefix+"fullscreenchange",e.isFullScreen=function(e){switch(O.undefined(e)&&(e=t.body),this.prefix){case"":return t.fullscreenElement===e;case"moz":return t.mozFullScreenElement===e;default:return t[this.prefix+"FullscreenElement"]===e}},e.requestFullScreen=function(e){return O.undefined(e)&&(e=t.body),""===this.prefix?e.requestFullScreen():e[this.prefix+("ms"===this.prefix?"RequestFullscreen":"RequestFullScreen")]()},e.cancelFullScreen=function(){return""===this.prefix?t.cancelFullScreen():t[this.prefix+("ms"===this.prefix?"ExitFullscreen":"CancelFullScreen")]()},e.element=function(){return""===this.prefix?t.fullscreenElement:t[this.prefix+"FullscreenElement"]}),e}function E(v,E){function A(e,t,n,r){h(e,t,n,x({},r,{plyr:Be}))}function j(t,n){E.debug&&e.console&&(n=Array.prototype.slice.call(n),O.string(E.logPrefix)&&E.logPrefix.length&&n.unshift(E.logPrefix),console[t].apply(console,n))}function V(){return{url:E.iconUrl,absolute:0===E.iconUrl.indexOf("http")||Ue.browser.isIE}}function R(){var e=[],t=V(),n=(t.absolute?"":t.url)+"#"+E.iconPrefix;return s(E.controls,"play-large")&&e.push('<button type="button" data-plyr="play" class="plyr__play-large">','<svg><use xlink:href="'+n+'-play" /></svg>','<span class="plyr__sr-only">'+E.i18n.play+"</span>","</button>"),e.push('<div class="plyr__controls">'),s(E.controls,"restart")&&e.push('<button type="button" data-plyr="restart">','<svg><use xlink:href="'+n+'-restart" /></svg>','<span class="plyr__sr-only">'+E.i18n.restart+"</span>","</button>"),s(E.controls,"rewind")&&e.push('<button type="button" data-plyr="rewind">','<svg><use xlink:href="'+n+'-rewind" /></svg>','<span class="plyr__sr-only">'+E.i18n.rewind+"</span>","</button>"),s(E.controls,"play")&&e.push('<button type="button" data-plyr="play">','<svg><use xlink:href="'+n+'-play" /></svg>','<span class="plyr__sr-only">'+E.i18n.play+"</span>","</button>",'<button type="button" data-plyr="pause">','<svg><use xlink:href="'+n+'-pause" /></svg>','<span class="plyr__sr-only">'+E.i18n.pause+"</span>","</button>"),s(E.controls,"fast-forward")&&e.push('<button type="button" data-plyr="fast-forward">','<svg><use xlink:href="'+n+'-fast-forward" /></svg>','<span class="plyr__sr-only">'+E.i18n.forward+"</span>","</button>"),s(E.controls,"progress")&&(e.push('<span class="plyr__progress">','<label for="seek{id}" class="plyr__sr-only">Seek</label>','<input id="seek{id}" class="plyr__progress--seek" type="range" min="0" max="100" step="0.1" value="0" data-plyr="seek">','<progress class="plyr__progress--played" max="100" value="0" role="presentation"></progress>','<progress class="plyr__progress--buffer" max="100" value="0">',"<span>0</span>% "+E.i18n.buffered,"</progress>"),E.tooltips.seek&&e.push('<span class="plyr__tooltip">00:00</span>'),e.push("</span>")),s(E.controls,"current-time")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+E.i18n.currentTime+"</span>",'<span class="plyr__time--current">00:00</span>',"</span>"),s(E.controls,"duration")&&e.push('<span class="plyr__time">','<span class="plyr__sr-only">'+E.i18n.duration+"</span>",'<span class="plyr__time--duration">00:00</span>',"</span>"),s(E.controls,"mute")&&e.push('<button type="button" data-plyr="mute">','<svg class="icon--muted"><use xlink:href="'+n+'-muted" /></svg>','<svg><use xlink:href="'+n+'-volume" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleMute+"</span>","</button>"),s(E.controls,"volume")&&e.push('<span class="plyr__volume">','<label for="volume{id}" class="plyr__sr-only">'+E.i18n.volume+"</label>",'<input id="volume{id}" class="plyr__volume--input" type="range" min="'+E.volumeMin+'" max="'+E.volumeMax+'" value="'+E.volume+'" data-plyr="volume">','<progress class="plyr__volume--display" max="'+E.volumeMax+'" value="'+E.volumeMin+'" role="presentation"></progress>',"</span>"),s(E.controls,"captions")&&e.push('<button type="button" data-plyr="captions">','<svg class="icon--captions-on"><use xlink:href="'+n+'-captions-on" /></svg>','<svg><use xlink:href="'+n+'-captions-off" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleCaptions+"</span>","</button>"),s(E.controls,"fullscreen")&&e.push('<button type="button" data-plyr="fullscreen">','<svg class="icon--exit-fullscreen"><use xlink:href="'+n+'-exit-fullscreen" /></svg>','<svg><use xlink:href="'+n+'-enter-fullscreen" /></svg>','<span class="plyr__sr-only">'+E.i18n.toggleFullscreen+"</span>","</button>"),e.push("</div>"),e.join("")}function q(){if(Ue.supported.full&&("audio"!==Ue.type||E.fullscreen.allowAudio)&&E.fullscreen.enabled){var e=N.supportsFullScreen;e||E.fullscreen.fallback&&!X()?(Je((e?"Native":"Fallback")+" fullscreen enabled"),m(Ue.container,E.classes.fullscreen.enabled,!0)):Je("Fullscreen not supported and fallback disabled"),Ue.buttons&&Ue.buttons.fullscreen&&k(Ue.buttons.fullscreen,!1),$()}}function D(){if("video"===Ue.type){U(E.selectors.captions)||Ue.videoContainer.insertAdjacentHTML("afterbegin",'<div class="'+p(E.selectors.captions)+'"></div>'),Ue.usingTextTracks=!1,Ue.media.textTracks&&(Ue.usingTextTracks=!0);for(var e,t="",n=Ue.media.childNodes,r=0;r<n.length;r++)"track"===n[r].nodeName.toLowerCase()&&(e=n[r].kind,"captions"!==e&&"subtitles"!==e||(t=n[r].getAttribute("src")));if(Ue.captionExists=!0,""===t?(Ue.captionExists=!1,Je("No caption track found")):Je("Caption track found; URI: "+t),Ue.captionExists){for(var a=Ue.media.textTracks,s=0;s<a.length;s++)a[s].mode="hidden";if(Y(Ue),(Ue.browser.isIE&&Ue.browser.version>=10||Ue.browser.isFirefox&&Ue.browser.version>=31)&&(Je("Detected browser with known TextTrack issues - using manual fallback"),Ue.usingTextTracks=!1),Ue.usingTextTracks){Je("TextTracks supported");for(var o=0;o<a.length;o++){var i=a[o];"captions"!==i.kind&&"subtitles"!==i.kind||g(i,"cuechange",function(){this.activeCues[0]&&"text"in this.activeCues[0]?H(this.activeCues[0].getCueAsHTML()):H()})}}else if(Je("TextTracks not supported so rendering captions manually"),Ue.currentCaption="",Ue.captions=[],""!==t){var l=new XMLHttpRequest;l.onreadystatechange=function(){if(4===l.readyState)if(200===l.status){var e,t=[],n=l.responseText,r="\r\n";n.indexOf(r+r)===-1&&(r=n.indexOf("\r\r")!==-1?"\r":"\n"),t=n.split(r+r);for(var a=0;a<t.length;a++){e=t[a],Ue.captions[a]=[];var s=e.split(r),o=0;s[o].indexOf(":")===-1&&(o=1),Ue.captions[a]=[s[o],s[o+1]]}Ue.captions.shift(),Je("Successfully loaded the caption file via AJAX")}else ze(E.logPrefix+"There was a problem loading the caption file via AJAX")},l.open("get",t,!0),l.send()}}else m(Ue.container,E.classes.captions.enabled)}}function H(e){var n=U(E.selectors.captions),r=t.createElement("span");n.innerHTML="",O.undefined(e)&&(e=""),O.string(e)?r.innerHTML=e.trim():r.appendChild(e),n.appendChild(r);n.offsetHeight}function W(e){function t(e,t){var n=[];n=e.split(" --> ");for(var r=0;r<n.length;r++)n[r]=n[r].replace(/(\d+:\d+:\d+\.\d+).*/,"$1");return a(n[t])}function n(e){return t(e,0)}function r(e){return t(e,1)}function a(e){if(null===e||void 0===e)return 0;var t,n=[],r=[];return n=e.split(","),r=n[0].split(":"),t=Math.floor(60*r[0]*60)+Math.floor(60*r[1])+Math.floor(r[2])}if(!Ue.usingTextTracks&&"video"===Ue.type&&Ue.supported.full&&(Ue.subcount=0,e=O.number(e)?e:Ue.media.currentTime,Ue.captions[Ue.subcount])){for(;r(Ue.captions[Ue.subcount][0])<e.toFixed(1);)if(Ue.subcount++,Ue.subcount>Ue.captions.length-1){Ue.subcount=Ue.captions.length-1;break}Ue.media.currentTime.toFixed(1)>=n(Ue.captions[Ue.subcount][0])&&Ue.media.currentTime.toFixed(1)<=r(Ue.captions[Ue.subcount][0])?(Ue.currentCaption=Ue.captions[Ue.subcount][1],H(Ue.currentCaption)):H()}}function Y(){if(Ue.buttons.captions){m(Ue.container,E.classes.captions.enabled,!0);var e=Ue.storage.captionsEnabled;O.boolean(e)||(e=E.captions.defaultActive),e&&(m(Ue.container,E.classes.captions.active,!0),k(Ue.buttons.captions,!0))}}function B(e){return Ue.container.querySelectorAll(e)}function U(e){return B(e)[0]}function X(){try{return e.self!==e.top}catch(e){return!0}}function $(){function e(e){9===e.which&&Ue.isFullscreen&&(e.target!==r||e.shiftKey?e.target===n&&e.shiftKey&&(e.preventDefault(),r.focus()):(e.preventDefault(),n.focus()))}var t=B("input:not([disabled]), button:not([disabled])"),n=t[0],r=t[t.length-1];g(Ue.container,"keydown",e)}function J(e,t){if(O.string(t))d(e,Ue.media,{src:t});else if(t.constructor===Array)for(var n=t.length-1;n>=0;n--)d(e,Ue.media,t[n])}function z(){if(E.loadSprite){var e=V();e.absolute?(Je("AJAX loading absolute SVG sprite"+(Ue.browser.isIE?" (due to IE)":"")),C(e.url,"sprite-plyr")):Je("Sprite will be used as external resource directly")}var n=E.html;Je("Injecting custom controls"),n||(n=R()),n=o(n,"{seektime}",E.seekTime),n=o(n,"{id}",Math.floor(1e4*Math.random()));var r;if(O.string(E.selectors.controls.container)&&(r=t.querySelector(E.selectors.controls.container)),O.htmlElement(r)||(r=Ue.container),r.insertAdjacentHTML("beforeend",n),E.tooltips.controls)for(var a=B([E.selectors.controls.wrapper," ",E.selectors.labels," .",E.classes.hidden].join("")),s=a.length-1;s>=0;s--){var i=a[s];m(i,E.classes.hidden,!1),m(i,E.classes.tooltip,!0)}}function G(){try{return Ue.controls=U(E.selectors.controls.wrapper),Ue.buttons={},Ue.buttons.seek=U(E.selectors.buttons.seek),Ue.buttons.play=B(E.selectors.buttons.play),Ue.buttons.pause=U(E.selectors.buttons.pause),Ue.buttons.restart=U(E.selectors.buttons.restart),Ue.buttons.rewind=U(E.selectors.buttons.rewind),Ue.buttons.forward=U(E.selectors.buttons.forward),Ue.buttons.fullscreen=U(E.selectors.buttons.fullscreen),Ue.buttons.mute=U(E.selectors.buttons.mute),Ue.buttons.captions=U(E.selectors.buttons.captions),Ue.progress={},Ue.progress.container=U(E.selectors.progress.container),Ue.progress.buffer={},Ue.progress.buffer.bar=U(E.selectors.progress.buffer),Ue.progress.buffer.text=Ue.progress.buffer.bar&&Ue.progress.buffer.bar.getElementsByTagName("span")[0],Ue.progress.played=U(E.selectors.progress.played),Ue.progress.tooltip=Ue.progress.container&&Ue.progress.container.querySelector("."+E.classes.tooltip),Ue.volume={},Ue.volume.input=U(E.selectors.volume.input),Ue.volume.display=U(E.selectors.volume.display),Ue.duration=U(E.selectors.duration),Ue.currentTime=U(E.selectors.currentTime),Ue.seekTime=B(E.selectors.seekTime),!0}catch(e){return ze("It looks like there is a problem with your controls HTML"),Q(!0),!1}}function K(){m(Ue.container,E.selectors.container.replace(".",""),Ue.supported.full)}function Q(e){e&&s(E.types.html5,Ue.type)?Ue.media.setAttribute("controls",""):Ue.media.removeAttribute("controls")}function Z(e){var t=E.i18n.play;if(O.string(E.title)&&E.title.length&&(t+=", "+E.title,Ue.container.setAttribute("aria-label",E.title)),Ue.supported.full&&Ue.buttons.play)for(var n=Ue.buttons.play.length-1;n>=0;n--)Ue.buttons.play[n].setAttribute("aria-label",t);O.htmlElement(e)&&e.setAttribute("title",E.i18n.frameTitle.replace("{title}",E.title))}function ee(){var t=null;Ue.storage={},L.supported&&E.storage.enabled&&(e.localStorage.removeItem("plyr-volume"),t=e.localStorage.getItem(E.storage.key),t&&(/^\d+(\.\d+)?$/.test(t)?te({volume:parseFloat(t)}):Ue.storage=JSON.parse(t)))}function te(t){L.supported&&E.storage.enabled&&(x(Ue.storage,t),e.localStorage.setItem(E.storage.key,JSON.stringify(Ue.storage)))}function ne(){if(!Ue.media)return void ze("No media element found!");if(Ue.supported.full&&(m(Ue.container,E.classes.type.replace("{0}",Ue.type),!0),s(E.types.embed,Ue.type)&&m(Ue.container,E.classes.type.replace("{0}","video"),!0),m(Ue.container,E.classes.stopped,E.autoplay),m(Ue.ontainer,E.classes.isIos,Ue.browser.isIos),m(Ue.container,E.classes.isTouch,Ue.browser.isTouch),"video"===Ue.type)){var e=t.createElement("div");e.setAttribute("class",E.classes.videoWrapper),i(Ue.media,e),Ue.videoContainer=e}s(E.types.embed,Ue.type)&&re()}function re(){var n,r=t.createElement("div"),s=Ue.type+"-"+Math.floor(1e4*Math.random());switch(Ue.type){case"youtube":n=T(Ue.embedId);break;case"vimeo":n=S(Ue.embedId);break;default:n=Ue.embedId}for(var o=B('[id^="'+Ue.type+'-"]'),i=o.length-1;i>=0;i--)l(o[i]);if(m(Ue.media,E.classes.videoWrapper,!0),m(Ue.media,E.classes.embedWrapper,!0),"youtube"===Ue.type)Ue.media.appendChild(r),r.setAttribute("id",s),O.object(e.YT)?se(n,r):(a(E.urls.youtube.api),e.onYouTubeReadyCallbacks=e.onYouTubeReadyCallbacks||[],e.onYouTubeReadyCallbacks.push(function(){se(n,r)}),e.onYouTubeIframeAPIReady=function(){e.onYouTubeReadyCallbacks.forEach(function(e){e()})});else if("vimeo"===Ue.type)if(Ue.supported.full?Ue.media.appendChild(r):r=Ue.media,r.setAttribute("id",s),O.object(e.Vimeo))oe(n,r);else{a(E.urls.vimeo.api);var u=e.setInterval(function(){O.object(e.Vimeo)&&(e.clearInterval(u),oe(n,r))},50)}else if("soundcloud"===Ue.type){var d=t.createElement("iframe");d.loaded=!1,g(d,"load",function(){d.loaded=!0}),c(d,{src:"https://w.soundcloud.com/player/?url=https://api.soundcloud.com/tracks/"+n,id:s}),r.appendChild(d),Ue.media.appendChild(r),e.SC||a(E.urls.soundcloud.api);var p=e.setInterval(function(){e.SC&&d.loaded&&(e.clearInterval(p),ie.call(d))},50)}}function ae(){Ue.supported.full&&(We(),Ye()),Z(U("iframe"))}function se(t,n){Ue.embed=new e.YT.Player(n.id,{videoId:t,playerVars:{autoplay:E.autoplay?1:0,controls:Ue.supported.full?0:1,rel:0,showinfo:0,iv_load_policy:3,cc_load_policy:E.captions.defaultActive?1:0,cc_lang_pref:"en",wmode:"transparent",modestbranding:1,disablekb:1,origin:"*"},events:{onError:function(e){A(Ue.container,"error",!0,{code:e.data,embed:e.target})},onReady:function(t){var n=t.target;Ue.media.play=function(){n.playVideo(),Ue.media.paused=!1},Ue.media.pause=function(){n.pauseVideo(),Ue.media.paused=!0},Ue.media.stop=function(){n.stopVideo(),Ue.media.paused=!0},Ue.media.duration=n.getDuration(),Ue.media.paused=!0,Ue.media.currentTime=0,Ue.media.muted=n.isMuted(),E.title=n.getVideoData().title,Ue.supported.full&&Ue.media.querySelector("iframe").setAttribute("tabindex","-1"),ae(),A(Ue.media,"timeupdate"),A(Ue.media,"durationchange"),e.clearInterval(Xe.buffering),Xe.buffering=e.setInterval(function(){Ue.media.buffered=n.getVideoLoadedFraction(),(null===Ue.media.lastBuffered||Ue.media.lastBuffered<Ue.media.buffered)&&A(Ue.media,"progress"),Ue.media.lastBuffered=Ue.media.buffered,1===Ue.media.buffered&&(e.clearInterval(Xe.buffering),A(Ue.media,"canplaythrough"))},200)},onStateChange:function(t){var n=t.target;switch(e.clearInterval(Xe.playing),t.data){case 0:Ue.media.paused=!0,A(Ue.media,"ended");break;case 1:Ue.media.paused=!1,Ue.media.seeking&&A(Ue.media,"seeked"),Ue.media.seeking=!1,A(Ue.media,"play"),A(Ue.media,"playing"),Xe.playing=e.setInterval(function(){Ue.media.currentTime=n.getCurrentTime(),A(Ue.media,"timeupdate")},100),Ue.media.duration!==n.getDuration()&&(Ue.media.duration=n.getDuration(),A(Ue.media,"durationchange"));break;case 2:Ue.media.paused=!0,A(Ue.media,"pause")}A(Ue.container,"statechange",!1,{code:t.data})}}})}function oe(t,n){Ue.embed=new e.Vimeo.Player(n,{id:parseInt(t),loop:E.loop,autoplay:E.autoplay,byline:!1,portrait:!1,title:!1}),Ue.media.play=function(){Ue.embed.play(),Ue.media.paused=!1},Ue.media.pause=function(){Ue.embed.pause(),Ue.media.paused=!0},Ue.media.stop=function(){Ue.embed.stop(),Ue.media.paused=!0},Ue.media.paused=!0,Ue.media.currentTime=0,ae(),Ue.embed.getCurrentTime().then(function(e){Ue.media.currentTime=e,A(Ue.media,"timeupdate")}),Ue.embed.getDuration().then(function(e){Ue.media.duration=e,A(Ue.media,"durationchange")}),Ue.embed.on("loaded",function(){O.htmlElement(Ue.embed.element)&&Ue.supported.full&&Ue.embed.element.setAttribute("tabindex","-1")}),Ue.embed.on("play",function(){Ue.media.paused=!1,A(Ue.media,"play"),A(Ue.media,"playing")}),Ue.embed.on("pause",function(){Ue.media.paused=!0,A(Ue.media,"pause")}),Ue.embed.on("timeupdate",function(e){Ue.media.seeking=!1,Ue.media.currentTime=e.seconds,A(Ue.media,"timeupdate")}),Ue.embed.on("progress",function(e){Ue.media.buffered=e.percent,A(Ue.media,"progress"),1===parseInt(e.percent)&&A(Ue.media,"canplaythrough")}),Ue.embed.on("seeked",function(){Ue.media.seeking=!1,A(Ue.media,"seeked"),A(Ue.media,"play")}),Ue.embed.on("ended",function(){Ue.media.paused=!0,A(Ue.media,"ended")})}function ie(){Ue.embed=e.SC.Widget(this),Ue.embed.bind(e.SC.Widget.Events.READY,function(){Ue.media.play=function(){Ue.embed.play(),Ue.media.paused=!1},Ue.media.pause=function(){Ue.embed.pause(),Ue.media.paused=!0},Ue.media.stop=function(){Ue.embed.seekTo(0),Ue.embed.pause(),Ue.media.paused=!0},Ue.media.paused=!0,Ue.media.currentTime=0,Ue.embed.getDuration(function(e){Ue.media.duration=e/1e3,ae()}),Ue.embed.getPosition(function(e){Ue.media.currentTime=e,A(Ue.media,"timeupdate")}),Ue.embed.bind(e.SC.Widget.Events.PLAY,function(){Ue.media.paused=!1,A(Ue.media,"play"),A(Ue.media,"playing")}),Ue.embed.bind(e.SC.Widget.Events.PAUSE,function(){Ue.media.paused=!0,A(Ue.media,"pause")}),Ue.embed.bind(e.SC.Widget.Events.PLAY_PROGRESS,function(e){Ue.media.seeking=!1,Ue.media.currentTime=e.currentPosition/1e3,A(Ue.media,"timeupdate")}),Ue.embed.bind(e.SC.Widget.Events.LOAD_PROGRESS,function(e){Ue.media.buffered=e.loadProgress,A(Ue.media,"progress"),1===parseInt(e.loadProgress)&&A(Ue.media,"canplaythrough")}),Ue.embed.bind(e.SC.Widget.Events.FINISH,function(){Ue.media.paused=!0,A(Ue.media,"ended")})})}function le(){"play"in Ue.media&&Ue.media.play()}function ue(){"pause"in Ue.media&&Ue.media.pause()}function ce(e){return O.boolean(e)||(e=Ue.media.paused),e?le():ue(),e}function de(e){O.number(e)||(e=E.seekTime),me(Ue.media.currentTime-e)}function pe(e){O.number(e)||(e=E.seekTime),me(Ue.media.currentTime+e)}function me(e){var t=0,n=Ue.media.paused,r=fe();O.number(e)?t=e:O.object(e)&&s(["input","change"],e.type)&&(t=e.target.value/e.target.max*r),t<0?t=0:t>r&&(t=r),Ne(t);try{Ue.media.currentTime=t.toFixed(4)}catch(e){}if(s(E.types.embed,Ue.type)){switch(Ue.type){case"youtube":Ue.embed.seekTo(t);break;case"vimeo":Ue.embed.setCurrentTime(t.toFixed(0));break;case"soundcloud":Ue.embed.seekTo(1e3*t)}n&&ue(),A(Ue.media,"timeupdate"),Ue.media.seeking=!0,A(Ue.media,"seeking")}Je("Seeking to "+Ue.media.currentTime+" seconds"),W(t)}function fe(){var e=parseInt(E.duration),t=0;return null===Ue.media.duration||isNaN(Ue.media.duration)||(t=Ue.media.duration),isNaN(e)?t:e}function ye(){m(Ue.container,E.classes.playing,!Ue.media.paused),m(Ue.container,E.classes.stopped,Ue.media.paused),Me(Ue.media.paused)}function be(){P={x:e.pageXOffset||0,y:e.pageYOffset||0}}function ve(){e.scrollTo(P.x,P.y)}function ge(e){var n=N.supportsFullScreen;if(n){if(!e||e.type!==N.fullScreenEventName)return N.isFullScreen(Ue.container)?N.cancelFullScreen():(be(),N.requestFullScreen(Ue.container)),void(Ue.isFullscreen=N.isFullScreen(Ue.container));Ue.isFullscreen=N.isFullScreen(Ue.container)}else Ue.isFullscreen=!Ue.isFullscreen,t.body.style.overflow=Ue.isFullscreen?"hidden":"";m(Ue.container,E.classes.fullscreen.active,Ue.isFullscreen),$(Ue.isFullscreen),Ue.buttons&&Ue.buttons.fullscreen&&k(Ue.buttons.fullscreen,Ue.isFullscreen),A(Ue.container,Ue.isFullscreen?"enterfullscreen":"exitfullscreen",!0),!Ue.isFullscreen&&n&&ve()}function he(e){if(O.boolean(e)||(e=!Ue.media.muted),k(Ue.buttons.mute,e),Ue.media.muted=e,0===Ue.media.volume&&ke(E.volume),s(E.types.embed,Ue.type)){switch(Ue.type){case"youtube":Ue.embed[Ue.media.muted?"mute":"unMute"]();break;case"vimeo":case"soundcloud":Ue.embed.setVolume(Ue.media.muted?0:parseFloat(E.volume/E.volumeMax))}A(Ue.media,"volumechange")}}function ke(e){var t=E.volumeMax,n=E.volumeMin;if(O.undefined(e)&&(e=Ue.storage.volume),(null===e||isNaN(e))&&(e=E.volume),e>t&&(e=t),e<n&&(e=n),Ue.media.volume=parseFloat(e/t),Ue.volume.display&&(Ue.volume.display.value=e),s(E.types.embed,Ue.type)){switch(Ue.type){case"youtube":Ue.embed.setVolume(100*Ue.media.volume);break;case"vimeo":case"soundcloud":Ue.embed.setVolume(Ue.media.volume)}A(Ue.media,"volumechange")}0===e?Ue.media.muted=!0:Ue.media.muted&&e>0&&he()}function we(e){var t=Ue.media.muted?0:Ue.media.volume*E.volumeMax;O.number(e)||(e=E.volumeStep),ke(t+e)}function xe(e){var t=Ue.media.muted?0:Ue.media.volume*E.volumeMax;O.number(e)||(e=E.volumeStep),ke(t-e)}function Te(){var e=Ue.media.muted?0:Ue.media.volume*E.volumeMax;Ue.supported.full&&(Ue.volume.input&&(Ue.volume.input.value=e),Ue.volume.display&&(Ue.volume.display.value=e)),te({volume:e}),m(Ue.container,E.classes.muted,0===e),Ue.supported.full&&Ue.buttons.mute&&k(Ue.buttons.mute,0===e)}function Se(e){Ue.supported.full&&Ue.buttons.captions&&(O.boolean(e)||(e=Ue.container.className.indexOf(E.classes.captions.active)===-1),Ue.captionsEnabled=e,k(Ue.buttons.captions,Ue.captionsEnabled),m(Ue.container,E.classes.captions.active,Ue.captionsEnabled),A(Ue.container,Ue.captionsEnabled?"captionsenabled":"captionsdisabled",!0),te({captionsEnabled:Ue.captionsEnabled}))}function _e(e){var t="waiting"===e.type;clearTimeout(Xe.loading),Xe.loading=setTimeout(function(){m(Ue.container,E.classes.loading,t),Me(t)},t?250:0)}function Ee(e){if(Ue.supported.full){var t=Ue.progress.played,n=0,r=fe();if(e)switch(e.type){case"timeupdate":case"seeking":if(Ue.controls.pressed)return;n=w(Ue.media.currentTime,r),"timeupdate"===e.type&&Ue.buttons.seek&&(Ue.buttons.seek.value=n);break;case"playing":case"progress":t=Ue.progress.buffer,n=function(){var e=Ue.media.buffered;return e&&e.length?w(e.end(0),r):O.number(e)?100*e:0}()}Ce(t,n)}}function Ce(e,t){if(Ue.supported.full){if(O.undefined(t)&&(t=0),O.undefined(e)){if(!Ue.progress||!Ue.progress.buffer)return;e=Ue.progress.buffer}O.htmlElement(e)?e.value=t:e&&(e.bar&&(e.bar.value=t),e.text&&(e.text.innerHTML=t))}}function Fe(e,t){if(t){isNaN(e)&&(e=0),Ue.secs=parseInt(e%60),Ue.mins=parseInt(e/60%60),Ue.hours=parseInt(e/60/60%60);var n=parseInt(fe()/60/60%60)>0;Ue.secs=("0"+Ue.secs).slice(-2),Ue.mins=("0"+Ue.mins).slice(-2),t.innerHTML=(n?Ue.hours+":":"")+Ue.mins+":"+Ue.secs}}function Ae(){if(Ue.supported.full){var e=fe()||0;!Ue.duration&&E.displayDuration&&Ue.media.paused&&Fe(e,Ue.currentTime),Ue.duration&&Fe(e,Ue.duration),Pe()}}function Ie(e){Fe(Ue.media.currentTime,Ue.currentTime),e&&"timeupdate"===e.type&&Ue.media.seeking||Ee(e)}function Ne(e){O.number(e)||(e=0);var t=fe(),n=w(e,t);Ue.progress&&Ue.progress.played&&(Ue.progress.played.value=n),Ue.buttons&&Ue.buttons.seek&&(Ue.buttons.seek.value=n)}function Pe(e){var t=fe();if(E.tooltips.seek&&Ue.progress.container&&0!==t){var n=Ue.progress.container.getBoundingClientRect(),r=0,a=E.classes.tooltip+"--visible";if(e)r=100/n.width*(e.pageX-n.left);else{if(!f(Ue.progress.tooltip,a))return;r=Ue.progress.tooltip.style.left.replace("%","")}r<0?r=0:r>100&&(r=100),Fe(t/100*r,Ue.progress.tooltip),Ue.progress.tooltip.style.left=r+"%",e&&s(["mouseenter","mouseleave"],e.type)&&m(Ue.progress.tooltip,a,"mouseenter"===e.type)}}function Me(t){if(E.hideControls&&"audio"!==Ue.type){var n=0,r=!1,a=t,o=f(Ue.container,E.classes.loading);if(O.boolean(t)||(t&&t.type?(r="enterfullscreen"===t.type,a=s(["mousemove","touchstart","mouseenter","focus"],t.type),s(["mousemove","touchmove"],t.type)&&(n=2e3),"focus"===t.type&&(n=3e3)):a=f(Ue.container,E.classes.hideControls)),e.clearTimeout(Xe.hover),a||Ue.media.paused||o){if(m(Ue.container,E.classes.hideControls,!1),Ue.media.paused||o)return;Ue.browser.isTouch&&(n=3e3)}a&&Ue.media.paused||(Xe.hover=e.setTimeout(function(){(!Ue.controls.pressed&&!Ue.controls.hover||r)&&m(Ue.container,E.classes.hideControls,!0)},n))}}function Oe(e){if(!O.undefined(e))return void Le(e);var t;switch(Ue.type){case"youtube":t=Ue.embed.getVideoUrl();break;case"vimeo":Ue.embed.getVideoUrl.then(function(e){t=e});break;case"soundcloud":Ue.embed.getCurrentSound(function(e){t=e.permalink_url});break;default:t=Ue.media.currentSrc}return t||""}function Le(e){function n(){if(Ue.embed=null,l(Ue.media),"video"===Ue.type&&Ue.videoContainer&&l(Ue.videoContainer),Ue.container&&Ue.container.removeAttribute("class"),"type"in e&&(Ue.type=e.type,"video"===Ue.type)){var n=e.sources[0];"type"in n&&s(E.types.embed,n.type)&&(Ue.type=n.type)}switch(Ue.supported=F(Ue.type),Ue.type){case"video":Ue.media=t.createElement("video");break;case"audio":Ue.media=t.createElement("audio");break;case"youtube":case"vimeo":case"soundcloud":Ue.media=t.createElement("div"),Ue.embedId=e.sources[0].src}u(Ue.container,Ue.media),O.boolean(e.autoplay)&&(E.autoplay=e.autoplay),s(E.types.html5,Ue.type)&&(E.crossorigin&&Ue.media.setAttribute("crossorigin",""),E.autoplay&&Ue.media.setAttribute("autoplay",""),"poster"in e&&Ue.media.setAttribute("poster",e.poster),E.loop&&Ue.media.setAttribute("loop","")),m(Ue.container,E.classes.fullscreen.active,Ue.isFullscreen),m(Ue.container,E.classes.captions.active,Ue.captionsEnabled),K(),s(E.types.html5,Ue.type)&&J("source",e.sources),ne(),s(E.types.html5,Ue.type)&&("tracks"in e&&J("track",e.tracks),Ue.media.load()),(s(E.types.html5,Ue.type)||s(E.types.embed,Ue.type)&&!Ue.supported.full)&&(We(),Ye()),E.title=e.title,Z()}return O.object(e)&&"sources"in e&&e.sources.length?(m(Ue.container,E.classes.ready,!1),ue(),Ne(),Ce(),qe(),void De(n,!1)):void ze("Invalid source format")}function je(e){"video"===Ue.type&&Ue.media.setAttribute("poster",e)}function Ve(){function n(){var e=ce(),t=Ue.buttons[e?"play":"pause"],n=Ue.buttons[e?"pause":"play"];if(n=n&&n.length>1?n[n.length-1]:n[0]){var r=f(t,E.classes.tabFocus);setTimeout(function(){n.focus(),r&&(m(t,E.classes.tabFocus,!1),m(n,E.classes.tabFocus,!0))},100)}}function r(){var e=t.activeElement;return e=e&&e!==t.body?t.querySelector(":focus"):null}function a(e){return e.keyCode?e.keyCode:e.which}function o(e){for(var t in Ue.buttons){var n=Ue.buttons[t];if(O.nodeList(n))for(var r=0;r<n.length;r++)m(n[r],E.classes.tabFocus,n[r]===e);else m(n,E.classes.tabFocus,n===e)}}function i(e){function t(){var e=Ue.media.duration;O.number(e)&&me(e/10*(n-48))}var n=a(e),r="keydown"===e.type,o=r&&n===u;if(O.number(n))if(r){var i=[48,49,50,51,52,53,54,56,57,32,75,38,40,77,39,37,70,67];switch(s(i,n)&&(e.preventDefault(),e.stopPropagation()),n){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:o||t();break;case 32:case 75:o||ce();break;case 38:we();break;case 40:xe();break;case 77:o||he();break;case 39:pe();break;case 37:de();break;case 70:ge();break;case 67:o||Se()}!N.supportsFullScreen&&Ue.isFullscreen&&27===n&&ge(),u=n}else u=null}var l=Ue.browser.isIE?"change":"input";if(E.keyboardShorcuts.focused){var u=null;E.keyboardShorcuts.global&&g(e,"keydown keyup",function(e){var t=a(e),n=r(),o=[48,49,50,51,52,53,54,56,57,75,77,70,67],l=I().length;1!==l||!s(o,t)||O.htmlElement(n)&&y(n,E.selectors.editable)||i(e)}),g(Ue.container,"keydown keyup",i)}g(e,"keyup",function(e){var t=a(e),n=r();9===t&&o(n)}),g(t.body,"click",function(){m(U("."+E.classes.tabFocus),E.classes.tabFocus,!1)});for(var c in Ue.buttons){var d=Ue.buttons[c];g(d,"blur",function(){m(d,"tab-focus",!1);
2
+ })}b(Ue.buttons.play,"click",E.listeners.play,n),b(Ue.buttons.pause,"click",E.listeners.pause,n),b(Ue.buttons.restart,"click",E.listeners.restart,me),b(Ue.buttons.rewind,"click",E.listeners.rewind,de),b(Ue.buttons.forward,"click",E.listeners.forward,pe),b(Ue.buttons.seek,l,E.listeners.seek,me),b(Ue.volume.input,l,E.listeners.volume,function(){ke(Ue.volume.input.value)}),b(Ue.buttons.mute,"click",E.listeners.mute,he),b(Ue.buttons.fullscreen,"click",E.listeners.fullscreen,ge),N.supportsFullScreen&&g(t,N.fullScreenEventName,ge),g(Ue.buttons.captions,"click",Se),g(Ue.progress.container,"mouseenter mouseleave mousemove",Pe),E.hideControls&&(g(Ue.container,"mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen",Me),g(Ue.controls,"mouseenter mouseleave",function(e){Ue.controls.hover="mouseenter"===e.type}),g(Ue.controls,"mousedown mouseup touchstart touchend touchcancel",function(e){Ue.controls.pressed=s(["mousedown","touchstart"],e.type)}),g(Ue.controls,"focus blur",Me,!0)),g(Ue.volume.input,"wheel",function(e){e.preventDefault();var t=e.webkitDirectionInvertedFromDevice,n=E.volumeStep/5;(e.deltaY<0||e.deltaX>0)&&(t?xe(n):we(n)),(e.deltaY>0||e.deltaX<0)&&(t?we(n):xe(n))})}function Re(){if(g(Ue.media,"timeupdate seeking",Ie),g(Ue.media,"timeupdate",W),g(Ue.media,"durationchange loadedmetadata",Ae),g(Ue.media,"ended",function(){"video"===Ue.type&&E.showPosterOnEnd&&("video"===Ue.type&&H(),me(),Ue.media.load())}),g(Ue.media,"progress playing",Ee),g(Ue.media,"volumechange",Te),g(Ue.media,"play pause ended",ye),g(Ue.media,"waiting canplay seeked",_e),E.clickToPlay&&"audio"!==Ue.type){var e=U("."+E.classes.videoWrapper);if(!e)return;e.style.cursor="pointer",g(e,"click",function(){E.hideControls&&Ue.browser.isTouch&&!Ue.media.paused||(Ue.media.paused?le():Ue.media.ended?(me(),le()):ue())})}E.disableContextMenu&&g(Ue.media,"contextmenu",function(e){e.preventDefault()}),g(Ue.media,E.events.concat(["keyup","keydown"]).join(" "),function(e){A(Ue.container,e.type,!0)})}function qe(){if(s(E.types.html5,Ue.type)){for(var e=Ue.media.querySelectorAll("source"),t=0;t<e.length;t++)l(e[t]);Ue.media.setAttribute("src","https://cdn.selz.com/plyr/blank.mp4"),Ue.media.load(),Je("Cancelled network requests")}}function De(n,r){function a(){clearTimeout(Xe.cleanUp),O.boolean(r)||(r=!0),O.function(n)&&n.call($e),r&&(Ue.init=!1,Ue.container.parentNode.replaceChild($e,Ue.container),t.body.style.overflow="",A($e,"destroyed",!0))}if(!Ue.init)return null;switch(Ue.type){case"youtube":e.clearInterval(Xe.buffering),e.clearInterval(Xe.playing),Ue.embed.destroy(),a();break;case"vimeo":Ue.embed.unload().then(a),Xe.cleanUp=e.setTimeout(a,200);break;case"video":case"audio":Q(!0),a()}}function He(){if(Ue.init)return null;if(N=_(),Ue.browser=n(),O.htmlElement(Ue.media)){ee();var e=v.tagName.toLowerCase();"div"===e?(Ue.type=v.getAttribute("data-type"),Ue.embedId=v.getAttribute("data-video-id"),v.removeAttribute("data-type"),v.removeAttribute("data-video-id")):(Ue.type=e,E.crossorigin=null!==v.getAttribute("crossorigin"),E.autoplay=E.autoplay||null!==v.getAttribute("autoplay"),E.loop=E.loop||null!==v.getAttribute("loop")),Ue.supported=F(Ue.type),Ue.supported.basic&&(Ue.container=i(v,t.createElement("div")),Ue.container.setAttribute("tabindex",0),K(),Je(""+Ue.browser.name+" "+Ue.browser.version),ne(),(s(E.types.html5,Ue.type)||s(E.types.embed,Ue.type)&&!Ue.supported.full)&&(We(),Ye(),Z()),Ue.init=!0)}}function We(){if(!Ue.supported.full)return ze("Basic support only",Ue.type),l(U(E.selectors.controls.wrapper)),l(U(E.selectors.buttons.play)),void Q(!0);var e=!B(E.selectors.controls.wrapper).length;e&&z(),G()&&(e&&Ve(),Re(),Q(),q(),D(),ke(),Te(),Ie(),ye())}function Ye(){e.setTimeout(function(){A(Ue.media,"ready")},0),m(Ue.media,M.classes.setup,!0),m(Ue.container,E.classes.ready,!0),Ue.media.plyr=Be,E.autoplay&&le()}var Be,Ue=this,Xe={};Ue.media=v;var $e=v.cloneNode(!0),Je=function(){j("log",arguments)},ze=function(){j("warn",arguments)};return Je("Config",E),Be={getOriginal:function(){return $e},getContainer:function(){return Ue.container},getEmbed:function(){return Ue.embed},getMedia:function(){return Ue.media},getType:function(){return Ue.type},getDuration:fe,getCurrentTime:function(){return Ue.media.currentTime},getVolume:function(){return Ue.media.volume},isMuted:function(){return Ue.media.muted},isReady:function(){return f(Ue.container,E.classes.ready)},isLoading:function(){return f(Ue.container,E.classes.loading)},isPaused:function(){return Ue.media.paused},on:function(e,t){return g(Ue.container,e,t),this},play:le,pause:ue,stop:function(){ue(),me()},restart:me,rewind:de,forward:pe,seek:me,source:Oe,poster:je,setVolume:ke,togglePlay:ce,toggleMute:he,toggleCaptions:Se,toggleFullscreen:ge,toggleControls:Me,isFullscreen:function(){return Ue.isFullscreen||!1},support:function(e){return r(Ue,e)},destroy:De},He(),Ue.init?Be:null}function C(e,n){var r=new XMLHttpRequest;if(!O.string(n)||!O.htmlElement(t.querySelector("#"+n))){var a=t.createElement("div");a.setAttribute("hidden",""),O.string(n)&&a.setAttribute("id",n),t.body.insertBefore(a,t.body.childNodes[0]),"withCredentials"in r&&(r.open("GET",e,!0),r.onload=function(){a.innerHTML=r.responseText},r.send())}}function F(e){var r=n(),a=r.isIE&&r.version<=9,s=r.isIos,o=r.isIphone,i=!!t.createElement("audio").canPlayType,l=!!t.createElement("video").canPlayType,u=!1,c=!1;switch(e){case"video":u=l,c=u&&!a&&!o;break;case"audio":u=i,c=u&&!a;break;case"vimeo":u=!0,c=!a&&!s;break;case"youtube":u=!0,c=!a&&!s,s&&!o&&r.version>=10&&(c=!0);break;case"soundcloud":u=!0,c=!a&&!o;break;default:u=i&&l,c=u&&!a}return{basic:u,full:c}}function A(e,n){function r(e,t){f(t,M.classes.hook)||a.push({target:e,media:t})}var a=[],s=[],o=[M.selectors.html5,M.selectors.embed].join(",");if(O.string(e)?e=t.querySelectorAll(e):O.htmlElement(e)?e=[e]:O.nodeList(e)||O.array(e)||O.string(e)||(O.undefined(n)&&O.object(e)&&(n=e),e=t.querySelectorAll(o)),O.nodeList(e)&&(e=Array.prototype.slice.call(e)),!F().basic||!e.length)return!1;for(var i=0;i<e.length;i++){var l=e[i],u=l.querySelectorAll(o);if(u.length)for(var c=0;c<u.length;c++)r(l,u[c]);else y(l,o)&&r(l,l)}return a.forEach(function(e){var t=e.target,r=e.media,a=!1;r===t&&(a=!0);var o={};try{o=JSON.parse(t.getAttribute("data-plyr"))}catch(e){}var i=x({},M,n,o);if(!i.enabled)return null;var l=new E(r,i);if(O.object(l)){if(i.debug){var u=i.events.concat(["setup","statechange","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled"]);g(l.getContainer(),u.join(" "),function(e){console.log([i.logPrefix,"event:",e.type].join(" "),e.detail.plyr)})}h(l.getContainer(),"setup",!0,{plyr:l}),s.push(l)}}),s}function I(e){if(O.string(e)?e=t.querySelector(e):O.undefined(e)&&(e=t.body),O.htmlElement(e)){var n=e.querySelectorAll("."+M.classes.setup),r=[];return Array.prototype.slice.call(n).forEach(function(e){O.object(e.plyr)&&r.push(e.plyr)}),r}return[]}var N,P={x:0,y:0},M={enabled:!0,debug:!1,autoplay:!1,loop:!1,seekTime:10,volume:10,volumeMin:0,volumeMax:10,volumeStep:1,duration:null,displayDuration:!0,loadSprite:!0,iconPrefix:"plyr",iconUrl:"https://cdn.plyr.io/2.0.10/plyr.svg",clickToPlay:!0,hideControls:!0,showPosterOnEnd:!1,disableContextMenu:!0,keyboardShorcuts:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},selectors:{html5:"video, audio",embed:"[data-type]",editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{seek:'[data-plyr="seek"]',play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',forward:'[data-plyr="fast-forward"]',mute:'[data-plyr="mute"]',captions:'[data-plyr="captions"]',fullscreen:'[data-plyr="fullscreen"]'},volume:{input:'[data-plyr="volume"]',display:".plyr__volume--display"},progress:{container:".plyr__progress",buffer:".plyr__progress--buffer",played:".plyr__progress--played"},captions:".plyr__captions",currentTime:".plyr__time--current",duration:".plyr__time--duration"},classes:{setup:"plyr--setup",ready:"plyr--ready",videoWrapper:"plyr__video-wrapper",embedWrapper:"plyr__video-embed",type:"plyr--{0}",stopped:"plyr--stopped",playing:"plyr--playing",muted:"plyr--muted",loading:"plyr--loading",hover:"plyr--hover",tooltip:"plyr__tooltip",hidden:"plyr__sr-only",hideControls:"plyr--hide-controls",isIos:"plyr--is-ios",isTouch:"plyr--is-touch",captions:{enabled:"plyr--captions-enabled",active:"plyr--captions-active"},fullscreen:{enabled:"plyr--fullscreen-enabled",active:"plyr--fullscreen-active"},tabFocus:"tab-focus"},captions:{defaultActive:!1},fullscreen:{enabled:!0,fallback:!0,allowAudio:!1},storage:{enabled:!0,key:"plyr"},controls:["play-large","play","progress","current-time","mute","volume","captions","fullscreen"],i18n:{restart:"Restart",rewind:"Rewind {seektime} secs",play:"Play",pause:"Pause",forward:"Forward {seektime} secs",played:"played",buffered:"buffered",currentTime:"Current time",duration:"Duration",volume:"Volume",toggleMute:"Toggle Mute",toggleCaptions:"Toggle Captions",toggleFullscreen:"Toggle Fullscreen",frameTitle:"Player for {title}"},types:{embed:["youtube","vimeo","soundcloud"],html5:["video","audio"]},urls:{vimeo:{api:"https://player.vimeo.com/api/player.js"},youtube:{api:"https://www.youtube.com/iframe_api"},soundcloud:{api:"https://w.soundcloud.com/player/api.js"}},listeners:{seek:null,play:null,pause:null,restart:null,rewind:null,forward:null,mute:null,volume:null,captions:null,fullscreen:null},events:["ready","ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied"],logPrefix:"[Plyr]"},O={object:function(e){return null!==e&&"object"==typeof e},array:function(e){return null!==e&&"object"==typeof e&&e.constructor===Array},number:function(e){return null!==e&&("number"==typeof e&&!isNaN(e-0)||"object"==typeof e&&e.constructor===Number)},string:function(e){return null!==e&&("string"==typeof e||"object"==typeof e&&e.constructor===String)},boolean:function(e){return null!==e&&"boolean"==typeof e},nodeList:function(e){return null!==e&&e instanceof NodeList},htmlElement:function(e){return null!==e&&e instanceof HTMLElement},function:function(e){return null!==e&&"function"==typeof e},undefined:function(e){return null!==e&&"undefined"==typeof e}},L={supported:function(){if(!("localStorage"in e))return!1;try{e.localStorage.setItem("___test","OK");var t=e.localStorage.getItem("___test");return e.localStorage.removeItem("___test"),"OK"===t}catch(e){return!1}return!1}()};return{setup:A,supported:F,loadSprite:C,get:I}}),function(){function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),n}"function"!=typeof window.CustomEvent&&(e.prototype=window.Event.prototype,window.CustomEvent=e)}();