plyr-rails 2.0.11 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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)}();