Merge branch 'develop' into a11y-improvements
# Conflicts: # demo/dist/demo.css # dist/plyr.css # dist/plyr.js.map # dist/plyr.min.js # dist/plyr.min.js.map # dist/plyr.polyfilled.js # dist/plyr.polyfilled.js.map # dist/plyr.polyfilled.min.js # dist/plyr.polyfilled.min.js.map # src/js/captions.js # src/js/plyr.js
This commit is contained in:
@ -32,6 +32,7 @@
|
|||||||
"message": "Use local parameter instead."
|
"message": "Use local parameter instead."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"no-param-reassign": [2, { "props": false }],
|
||||||
"array-bracket-newline": [2, { "minItems": 2 }],
|
"array-bracket-newline": [2, { "minItems": 2 }],
|
||||||
"array-element-newline": [2, { "minItems": 2 }]
|
"array-element-newline": [2, { "minItems": 2 }]
|
||||||
},
|
},
|
||||||
|
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@ -1,8 +1,8 @@
|
|||||||
### Link to related issue (if applicable)
|
### Link to related issue (if applicable)
|
||||||
|
|
||||||
### Sumary of proposed changes
|
### Summary of proposed changes
|
||||||
|
|
||||||
### Task list
|
### Checklist
|
||||||
|
- [ ] Use `develop` as the base branch
|
||||||
- [ ] Tested on [supported browsers](https://github.com/sampotts/plyr#browser-support)
|
- [ ] Exclude the gulp build from the PR
|
||||||
- [ ] Gulp build completed
|
- [ ] Test on [supported browsers](https://github.com/sampotts/plyr#browser-support)
|
||||||
|
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 'lts/*'
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm run lint
|
||||||
|
- npm run build
|
21
changelog.md
21
changelog.md
@ -1,8 +1,27 @@
|
|||||||
|
# v3.3.10
|
||||||
|
|
||||||
|
* Fix for buffer display alignment and incorrect BEM classname
|
||||||
|
* Fix for playback not resuming position after quality swap (fixes #991, thanks @philipgiuliani!)
|
||||||
|
* Travis integration (thanks @friday!)
|
||||||
|
* Translate quality badges and quality names (thanks @philipgiuliani!)
|
||||||
|
* Improve captions handling for streaming (thanks @friday!)
|
||||||
|
* Call duration update method manually if user config has duration (thanks @friday!)
|
||||||
|
|
||||||
|
# v3.3.9
|
||||||
|
|
||||||
|
Again, more changes from @friday!
|
||||||
|
|
||||||
|
* Restore window reference in `utils.is.cue()`
|
||||||
|
* Fix InvalidStateError and IE11 issues
|
||||||
|
* Respect storage being disabled for storage getter
|
||||||
|
|
||||||
# v3.3.8
|
# v3.3.8
|
||||||
|
|
||||||
|
Many changes here thanks to @friday:
|
||||||
|
|
||||||
* Added missing URL polyfill
|
* Added missing URL polyfill
|
||||||
* Pause while seeking to mimic default HTML5 behaviour
|
* Pause while seeking to mimic default HTML5 behaviour
|
||||||
* Add 'seeked' event listener to update progress (fixes #966)
|
* Add `seeked` event listener to update progress (fixes #966)
|
||||||
* Trigger seeked event in youtube plugin if either playing or paused (fixes #921)
|
* Trigger seeked event in youtube plugin if either playing or paused (fixes #921)
|
||||||
* Fix for YouTube and Vimeo autoplays on seek (fixes #876)
|
* Fix for YouTube and Vimeo autoplays on seek (fixes #876)
|
||||||
* Toggle controls improvements
|
* Toggle controls improvements
|
||||||
|
@ -116,9 +116,8 @@ const controls = `
|
|||||||
<span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span>
|
<span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="plyr__progress">
|
<div class="plyr__progress">
|
||||||
<label for="plyr-seek-{id}" class="plyr__sr-only">Seek</label>
|
<input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
|
||||||
<input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" id="plyr-seek-{id}">
|
<progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
|
||||||
<progress class="plyr__progress--buffer" min="0" max="100" value="0">% buffered</progress>
|
|
||||||
<span role="tooltip" class="plyr__tooltip">00:00</span>
|
<span role="tooltip" class="plyr__tooltip">00:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
|
<div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
|
||||||
@ -130,8 +129,7 @@ const controls = `
|
|||||||
<span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
|
<span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="plyr__volume">
|
<div class="plyr__volume">
|
||||||
<label for="plyr-volume-{id}" class="plyr__sr-only">Volume</label>
|
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume">
|
||||||
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" id="plyr-volume-{id}">
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="plyr__control" aria-label="Enable captions" data-plyr="captions">
|
<button type="button" class="plyr__control" aria-label="Enable captions" data-plyr="captions">
|
||||||
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
|
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
|
||||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
329
dist/plyr.js
vendored
329
dist/plyr.js
vendored
@ -407,7 +407,7 @@ var Storage = function () {
|
|||||||
createClass(Storage, [{
|
createClass(Storage, [{
|
||||||
key: 'get',
|
key: 'get',
|
||||||
value: function get$$1(key) {
|
value: function get$$1(key) {
|
||||||
if (!Storage.supported) {
|
if (!Storage.supported || !this.enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,7 +526,7 @@ var utils = {
|
|||||||
return this.instanceof(input, Event);
|
return this.instanceof(input, Event);
|
||||||
},
|
},
|
||||||
cue: function cue(input) {
|
cue: function cue(input) {
|
||||||
return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue);
|
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
|
||||||
},
|
},
|
||||||
track: function track(input) {
|
track: function track(input) {
|
||||||
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
|
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
|
||||||
@ -637,26 +637,25 @@ var utils = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix = 'cache-';
|
var prefix = 'cache';
|
||||||
var hasId = utils.is.string(id);
|
var hasId = utils.is.string(id);
|
||||||
var isCached = false;
|
var isCached = false;
|
||||||
|
|
||||||
var exists = function exists() {
|
var exists = function exists() {
|
||||||
return document.querySelectorAll('#' + id).length;
|
return document.getElementById(id) !== null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function injectSprite(data) {
|
var update = function update(container, data) {
|
||||||
|
container.innerHTML = data;
|
||||||
|
|
||||||
// Check again incase of race condition
|
// Check again incase of race condition
|
||||||
if (hasId && exists()) {
|
if (hasId && exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject content
|
|
||||||
this.innerHTML = data;
|
|
||||||
|
|
||||||
// Inject the SVG to the body
|
// Inject the SVG to the body
|
||||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
document.body.insertAdjacentElement('afterbegin', container);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Only load once if ID set
|
// Only load once if ID set
|
||||||
if (!hasId || !exists()) {
|
if (!hasId || !exists()) {
|
||||||
@ -672,13 +671,12 @@ var utils = {
|
|||||||
|
|
||||||
// Check in cache
|
// Check in cache
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
var cached = window.localStorage.getItem(prefix + id);
|
var cached = window.localStorage.getItem(prefix + '-' + id);
|
||||||
isCached = cached !== null;
|
isCached = cached !== null;
|
||||||
|
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
var data = JSON.parse(cached);
|
var data = JSON.parse(cached);
|
||||||
injectSprite.call(container, data.content);
|
update(container, data.content);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,12 +687,12 @@ var utils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
window.localStorage.setItem(prefix + id, JSON.stringify({
|
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
|
||||||
content: result
|
content: result
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
injectSprite.call(container, result);
|
update(container, result);
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1275,6 +1273,14 @@ var utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Get a nested value in an object
|
||||||
|
getDeep: function getDeep(object, path) {
|
||||||
|
return path.split('.').reduce(function (obj, key) {
|
||||||
|
return obj && obj[key];
|
||||||
|
}, object);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// Get the closest value in an array
|
// Get the closest value in an array
|
||||||
closest: function closest(array, value) {
|
closest: function closest(array, value) {
|
||||||
if (!utils.is.array(array) || !array.length) {
|
if (!utils.is.array(array) || !array.length) {
|
||||||
@ -1694,6 +1700,13 @@ var html5 = {
|
|||||||
|
|
||||||
player.media.src = supported[0].getAttribute('src');
|
player.media.src = supported[0].getAttribute('src');
|
||||||
|
|
||||||
|
// Restore time
|
||||||
|
var onLoadedMetaData = function onLoadedMetaData() {
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
player.off('loadedmetadata', onLoadedMetaData);
|
||||||
|
};
|
||||||
|
player.on('loadedmetadata', onLoadedMetaData);
|
||||||
|
|
||||||
// Load new source
|
// Load new source
|
||||||
player.media.load();
|
player.media.load();
|
||||||
|
|
||||||
@ -1702,9 +1715,6 @@ var html5 = {
|
|||||||
player.play();
|
player.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore time
|
|
||||||
player.currentTime = currentTime;
|
|
||||||
|
|
||||||
// Trigger change event
|
// Trigger change event
|
||||||
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
quality: input
|
quality: input
|
||||||
@ -1746,11 +1756,15 @@ var i18n = {
|
|||||||
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
|
if (utils.is.empty(key) || utils.is.empty(config)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var string = config.i18n[key];
|
var string = utils.getDeep(config.i18n, key);
|
||||||
|
|
||||||
|
if (utils.is.empty(string)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
var replace = {
|
var replace = {
|
||||||
'{seektime}': config.seekTime,
|
'{seektime}': config.seekTime,
|
||||||
@ -2001,9 +2015,6 @@ var controls = {
|
|||||||
// Label/Tooltip
|
// Label/Tooltip
|
||||||
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
|
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
|
||||||
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
|
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
|
||||||
|
|
||||||
// Add aria attributes
|
|
||||||
attributes['aria-pressed'] = false;
|
|
||||||
} else {
|
} else {
|
||||||
button.appendChild(controls.createIcon.call(this, icon));
|
button.appendChild(controls.createIcon.call(this, icon));
|
||||||
button.appendChild(controls.createLabel.call(this, label));
|
button.appendChild(controls.createLabel.call(this, label));
|
||||||
@ -2025,19 +2036,26 @@ var controls = {
|
|||||||
this.elements.buttons[type] = button;
|
this.elements.buttons[type] = button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle classname when pressed property is set
|
||||||
|
var className = this.config.classNames.controlPressed;
|
||||||
|
Object.defineProperty(button, 'pressed', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function get$$1() {
|
||||||
|
return utils.hasClass(button, className);
|
||||||
|
},
|
||||||
|
set: function set$$1() {
|
||||||
|
var pressed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||||
|
|
||||||
|
utils.toggleClass(button, className, pressed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Create an <input type='range'>
|
// Create an <input type='range'>
|
||||||
createRange: function createRange(type, attributes) {
|
createRange: function createRange(type, attributes) {
|
||||||
// Seek label
|
|
||||||
var label = utils.createElement('label', {
|
|
||||||
for: attributes.id,
|
|
||||||
id: attributes.id + '-label',
|
|
||||||
class: this.config.classNames.hidden
|
|
||||||
}, i18n.get(type, this.config));
|
|
||||||
|
|
||||||
// Seek input
|
// Seek input
|
||||||
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
||||||
type: 'range',
|
type: 'range',
|
||||||
@ -2048,7 +2066,7 @@ var controls = {
|
|||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||||
role: 'slider',
|
role: 'slider',
|
||||||
'aria-labelledby': attributes.id + '-label',
|
'aria-label': i18n.get(type, this.config),
|
||||||
'aria-valuemin': 0,
|
'aria-valuemin': 0,
|
||||||
'aria-valuemax': 100,
|
'aria-valuemax': 100,
|
||||||
'aria-valuenow': 0
|
'aria-valuenow': 0
|
||||||
@ -2059,10 +2077,7 @@ var controls = {
|
|||||||
// Set the fill for webkit now
|
// Set the fill for webkit now
|
||||||
controls.updateRangeFill.call(this, input);
|
controls.updateRangeFill.call(this, input);
|
||||||
|
|
||||||
return {
|
return input;
|
||||||
label: label,
|
|
||||||
input: input
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -2185,7 +2200,7 @@ var controls = {
|
|||||||
|
|
||||||
// Update mute state
|
// Update mute state
|
||||||
if (utils.is.element(this.elements.buttons.mute)) {
|
if (utils.is.element(this.elements.buttons.mute)) {
|
||||||
utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
|
this.elements.buttons.mute.pressed = this.muted || this.volume === 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2424,27 +2439,7 @@ var controls = {
|
|||||||
|
|
||||||
// Get the badge HTML for HD, 4K etc
|
// Get the badge HTML for HD, 4K etc
|
||||||
var getBadge = function getBadge(quality) {
|
var getBadge = function getBadge(quality) {
|
||||||
var label = '';
|
var label = i18n.get('qualityBadge.' + quality, _this3.config);
|
||||||
|
|
||||||
switch (quality) {
|
|
||||||
case 2160:
|
|
||||||
label = '4K';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1440:
|
|
||||||
case 1080:
|
|
||||||
case 720:
|
|
||||||
label = 'HD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 576:
|
|
||||||
case 480:
|
|
||||||
label = 'SD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label.length) {
|
if (!label.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -2467,7 +2462,6 @@ var controls = {
|
|||||||
|
|
||||||
|
|
||||||
// Translate a value into a nice label
|
// Translate a value into a nice label
|
||||||
// TODO: Localisation
|
|
||||||
getLabel: function getLabel(setting, value) {
|
getLabel: function getLabel(setting, value) {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'speed':
|
case 'speed':
|
||||||
@ -2475,7 +2469,13 @@ var controls = {
|
|||||||
|
|
||||||
case 'quality':
|
case 'quality':
|
||||||
if (utils.is.number(value)) {
|
if (utils.is.number(value)) {
|
||||||
return value + 'p';
|
var label = i18n.get('qualityLabel.' + value, this.config);
|
||||||
|
|
||||||
|
if (!label.length) {
|
||||||
|
return value + 'p';
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.toTitleCase(value);
|
return utils.toTitleCase(value);
|
||||||
@ -2635,12 +2635,7 @@ var controls = {
|
|||||||
|
|
||||||
// Generate options
|
// Generate options
|
||||||
tracks.forEach(function (track) {
|
tracks.forEach(function (track) {
|
||||||
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.captions.language.toLowerCase());
|
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.language);
|
||||||
});
|
|
||||||
|
|
||||||
// Store reference
|
|
||||||
this.options.captions = tracks.map(function (track) {
|
|
||||||
return track.language;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
@ -2909,11 +2904,9 @@ var controls = {
|
|||||||
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
|
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
|
||||||
|
|
||||||
// Seek range slider
|
// Seek range slider
|
||||||
var seek = controls.createRange.call(this, 'seek', {
|
progress.appendChild(controls.createRange.call(this, 'seek', {
|
||||||
id: 'plyr-seek-' + data.id
|
id: 'plyr-seek-' + data.id
|
||||||
});
|
}));
|
||||||
progress.appendChild(seek.label);
|
|
||||||
progress.appendChild(seek.input);
|
|
||||||
|
|
||||||
// Buffer progress
|
// Buffer progress
|
||||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
||||||
@ -2963,11 +2956,9 @@ var controls = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create the volume range slider
|
// Create the volume range slider
|
||||||
var range = controls.createRange.call(this, 'volume', utils.extend(attributes, {
|
volume.appendChild(controls.createRange.call(this, 'volume', utils.extend(attributes, {
|
||||||
id: 'plyr-volume-' + data.id
|
id: 'plyr-volume-' + data.id
|
||||||
}));
|
})));
|
||||||
volume.appendChild(range.label);
|
|
||||||
volume.appendChild(range.input);
|
|
||||||
|
|
||||||
this.elements.volume = volume;
|
this.elements.volume = volume;
|
||||||
|
|
||||||
@ -3243,28 +3234,6 @@ var captions = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default language if not set
|
|
||||||
var stored = this.storage.get('language');
|
|
||||||
|
|
||||||
if (!utils.is.empty(stored)) {
|
|
||||||
this.captions.language = stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utils.is.empty(this.captions.language)) {
|
|
||||||
this.captions.language = this.config.captions.language.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set captions enabled state if not set
|
|
||||||
if (!utils.is.boolean(this.captions.active)) {
|
|
||||||
var active = this.storage.get('captions');
|
|
||||||
|
|
||||||
if (utils.is.boolean(active)) {
|
|
||||||
this.captions.active = active;
|
|
||||||
} else {
|
|
||||||
this.captions.active = this.config.captions.active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only Vimeo and HTML5 video supported at this point
|
// Only Vimeo and HTML5 video supported at this point
|
||||||
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
|
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
|
||||||
// Clear menu and hide
|
// Clear menu and hide
|
||||||
@ -3282,17 +3251,6 @@ var captions = {
|
|||||||
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the class hook
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
|
||||||
|
|
||||||
// Get tracks
|
|
||||||
var tracks = captions.getTracks.call(this);
|
|
||||||
|
|
||||||
// If no caption file exists, hide container for caption text
|
|
||||||
if (utils.is.empty(tracks)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get browser info
|
// Get browser info
|
||||||
var browser = utils.getBrowser();
|
var browser = utils.getBrowser();
|
||||||
|
|
||||||
@ -3315,14 +3273,52 @@ var captions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set language
|
// Try to load the value from storage
|
||||||
captions.setLanguage.call(this);
|
var active = this.storage.get('captions');
|
||||||
|
|
||||||
// Enable UI
|
// Otherwise fall back to the default config
|
||||||
captions.show.call(this);
|
if (!utils.is.boolean(active)) {
|
||||||
|
active = this.config.captions.active;
|
||||||
|
}
|
||||||
|
|
||||||
// Set available languages in list
|
// Set toggled state
|
||||||
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
this.toggleCaptions(active);
|
||||||
|
|
||||||
|
// Watch changes to textTracks and update captions menu
|
||||||
|
if (this.config.captions.update) {
|
||||||
|
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
||||||
|
setTimeout(captions.update.bind(this), 0);
|
||||||
|
},
|
||||||
|
update: function update() {
|
||||||
|
// Update tracks
|
||||||
|
var tracks = captions.getTracks.call(this);
|
||||||
|
this.options.captions = tracks.map(function (_ref) {
|
||||||
|
var language = _ref.language;
|
||||||
|
return language;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set language if it hasn't been set already
|
||||||
|
if (!this.language) {
|
||||||
|
var language = this.config.captions.language;
|
||||||
|
|
||||||
|
if (language === 'auto') {
|
||||||
|
var _split = (navigator.language || navigator.userLanguage).split('-');
|
||||||
|
|
||||||
|
var _split2 = slicedToArray(_split, 1);
|
||||||
|
|
||||||
|
language = _split2[0];
|
||||||
|
}
|
||||||
|
this.language = this.storage.get('language') || (language || '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle the class hooks
|
||||||
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||||
|
|
||||||
|
// Update available languages in list
|
||||||
|
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3483,25 +3479,6 @@ var captions = {
|
|||||||
} else {
|
} else {
|
||||||
this.debug.warn('No captions element to render to');
|
this.debug.warn('No captions element to render to');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// Display captions container and button (for initialization)
|
|
||||||
show: function show() {
|
|
||||||
// Try to load the value from storage
|
|
||||||
var active = this.storage.get('captions');
|
|
||||||
|
|
||||||
// Otherwise fall back to the default config
|
|
||||||
if (!utils.is.boolean(active)) {
|
|
||||||
active = this.config.captions.active;
|
|
||||||
} else {
|
|
||||||
this.captions.active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true);
|
|
||||||
utils.toggleState(this.elements.buttons.captions, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3607,7 +3584,7 @@ var defaults$1 = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.3.10/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -3646,7 +3623,10 @@ var defaults$1 = {
|
|||||||
// Captions settings
|
// Captions settings
|
||||||
captions: {
|
captions: {
|
||||||
active: false,
|
active: false,
|
||||||
language: (navigator.language || navigator.userLanguage).split('-')[0]
|
language: 'auto',
|
||||||
|
// Listen to new tracks added after Plyr is initialized.
|
||||||
|
// This is needed for streaming captions, but may result in unselectable options
|
||||||
|
update: false
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fullscreen settings
|
// Fullscreen settings
|
||||||
@ -3703,7 +3683,15 @@ var defaults$1 = {
|
|||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
advertisement: 'Ad'
|
advertisement: 'Ad',
|
||||||
|
qualityBadge: {
|
||||||
|
2160: '4K',
|
||||||
|
1440: 'HD',
|
||||||
|
1080: 'HD',
|
||||||
|
720: 'HD',
|
||||||
|
576: 'SD',
|
||||||
|
480: 'SD'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -3791,9 +3779,8 @@ var defaults$1 = {
|
|||||||
display: {
|
display: {
|
||||||
currentTime: '.plyr__time--current',
|
currentTime: '.plyr__time--current',
|
||||||
duration: '.plyr__time--duration',
|
duration: '.plyr__time--duration',
|
||||||
buffer: '.plyr__progress--buffer',
|
buffer: '.plyr__progress__buffer',
|
||||||
played: '.plyr__progress--played',
|
loop: '.plyr__progress__loop', // Used later
|
||||||
loop: '.plyr__progress--loop',
|
|
||||||
volume: '.plyr__volume--display'
|
volume: '.plyr__volume--display'
|
||||||
},
|
},
|
||||||
progress: '.plyr__progress',
|
progress: '.plyr__progress',
|
||||||
@ -4163,8 +4150,10 @@ var ui = {
|
|||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Captions
|
// Setup captions for HTML5
|
||||||
captions.setup.call(this);
|
if (this.isHTML5) {
|
||||||
|
captions.setup.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset volume
|
// Reset volume
|
||||||
this.volume = null;
|
this.volume = null;
|
||||||
@ -4217,6 +4206,12 @@ var ui = {
|
|||||||
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
||||||
ui.setPoster.call(this, this.poster);
|
ui.setPoster.call(this, this.poster);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually set the duration if user has overridden it.
|
||||||
|
// The event listeners for it doesn't get called if preload is disabled (#701)
|
||||||
|
if (this.config.duration) {
|
||||||
|
controls.durationUpdate.call(this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -4410,7 +4405,7 @@ var Listeners = function () {
|
|||||||
// and if the focused element is not editable (e.g. text input)
|
// and if the focused element is not editable (e.g. text input)
|
||||||
// and any that accept key input http://webaim.org/techniques/keyboard/
|
// and any that accept key input http://webaim.org/techniques/keyboard/
|
||||||
var focused = utils.getFocusElement();
|
var focused = utils.getFocusElement();
|
||||||
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
|
if (utils.is.element(focused) && focused !== this.player.elements.inputs.seek && utils.matches(focused, this.player.config.selectors.editable)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4904,6 +4899,12 @@ var Listeners = function () {
|
|||||||
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
|
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
|
||||||
var seek = event.currentTarget;
|
var seek = event.currentTarget;
|
||||||
|
|
||||||
|
var code = event.keyCode ? event.keyCode : event.which;
|
||||||
|
var eventType = event.type;
|
||||||
|
|
||||||
|
if ((eventType === 'keydown' || eventType === 'keyup') && code !== 39 && code !== 37) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Was playing before?
|
// Was playing before?
|
||||||
var play = seek.hasAttribute('play-on-seeked');
|
var play = seek.hasAttribute('play-on-seeked');
|
||||||
|
|
||||||
@ -7278,24 +7279,19 @@ var Plyr = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
// If the method is called without parameter, toggle based on current value
|
||||||
var show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
var active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
||||||
|
|
||||||
// Nothing to change...
|
|
||||||
if (this.captions.active === show) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set global
|
|
||||||
this.captions.active = show;
|
|
||||||
|
|
||||||
// Toggle state
|
// Toggle state
|
||||||
utils.toggleState(this.elements.buttons.captions, this.captions.active);
|
this.elements.buttons.captions.pressed = active;
|
||||||
|
|
||||||
// Add class hook
|
// Add class hook
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active);
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active);
|
||||||
|
|
||||||
// Trigger an event
|
// Update state and trigger event
|
||||||
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
if (active !== this.captions.active) {
|
||||||
|
this.captions.active = active;
|
||||||
|
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7583,21 +7579,16 @@ var Plyr = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'currentTime',
|
key: 'currentTime',
|
||||||
set: function set$$1(input) {
|
set: function set$$1(input) {
|
||||||
var targetTime = 0;
|
// Bail if media duration isn't available yet
|
||||||
|
if (!this.duration) {
|
||||||
if (utils.is.number(input)) {
|
return;
|
||||||
targetTime = input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalise targetTime
|
// Validate input
|
||||||
if (targetTime < 0) {
|
var inputIsValid = utils.is.number(input) && input > 0;
|
||||||
targetTime = 0;
|
|
||||||
} else if (targetTime > this.duration) {
|
|
||||||
targetTime = this.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set
|
// Set
|
||||||
this.media.currentTime = targetTime;
|
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
|
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
|
||||||
@ -7656,11 +7647,11 @@ var Plyr = function () {
|
|||||||
// Faux duration set via config
|
// Faux duration set via config
|
||||||
var fauxDuration = parseFloat(this.config.duration);
|
var fauxDuration = parseFloat(this.config.duration);
|
||||||
|
|
||||||
// True duration
|
// Media duration can be NaN before the media has loaded
|
||||||
var realDuration = this.media ? Number(this.media.duration) : 0;
|
var duration = (this.media || {}).duration || 0;
|
||||||
|
|
||||||
// If custom duration is funky, use regular duration
|
// If config duration is funky, use regular duration
|
||||||
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
return fauxDuration || duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7832,7 +7823,7 @@ var Plyr = function () {
|
|||||||
quality = Number(input);
|
quality = Number(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils.is.number(quality) || quality === 0) {
|
if (!utils.is.number(quality)) {
|
||||||
quality = this.storage.get('quality');
|
quality = this.storage.get('quality');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js
vendored
2
dist/plyr.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js.map
vendored
2
dist/plyr.min.js.map
vendored
File diff suppressed because one or more lines are too long
391
dist/plyr.polyfilled.js
vendored
391
dist/plyr.polyfilled.js
vendored
@ -20,7 +20,7 @@ if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef
|
|||||||
});
|
});
|
||||||
|
|
||||||
var _core = createCommonjsModule(function (module) {
|
var _core = createCommonjsModule(function (module) {
|
||||||
var core = module.exports = { version: '2.5.6' };
|
var core = module.exports = { version: '2.5.3' };
|
||||||
if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
|
if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
|
||||||
});
|
});
|
||||||
var _core_1 = _core.version;
|
var _core_1 = _core.version;
|
||||||
@ -333,18 +333,11 @@ var _arrayIncludes = function (IS_INCLUDES) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _shared = createCommonjsModule(function (module) {
|
|
||||||
var SHARED = '__core-js_shared__';
|
var SHARED = '__core-js_shared__';
|
||||||
var store = _global[SHARED] || (_global[SHARED] = {});
|
var store = _global[SHARED] || (_global[SHARED] = {});
|
||||||
|
var _shared = function (key) {
|
||||||
(module.exports = function (key, value) {
|
return store[key] || (store[key] = {});
|
||||||
return store[key] || (store[key] = value !== undefined ? value : {});
|
};
|
||||||
})('versions', []).push({
|
|
||||||
version: _core.version,
|
|
||||||
mode: _library ? 'pure' : 'global',
|
|
||||||
copyright: '© 2018 Denis Pushkarev (zloirock.ru)'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var shared = _shared('keys');
|
var shared = _shared('keys');
|
||||||
|
|
||||||
@ -998,7 +991,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC
|
|||||||
var VALUES_BUG = false;
|
var VALUES_BUG = false;
|
||||||
var proto = Base.prototype;
|
var proto = Base.prototype;
|
||||||
var $native = proto[ITERATOR$2] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT];
|
var $native = proto[ITERATOR$2] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT];
|
||||||
var $default = $native || getMethod(DEFAULT);
|
var $default = (!BUGGY && $native) || getMethod(DEFAULT);
|
||||||
var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined;
|
var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined;
|
||||||
var $anyNative = NAME == 'Array' ? proto.entries || $native : $native;
|
var $anyNative = NAME == 'Array' ? proto.entries || $native : $native;
|
||||||
var methods, key, IteratorPrototype;
|
var methods, key, IteratorPrototype;
|
||||||
@ -1009,7 +1002,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC
|
|||||||
// Set @@toStringTag to native iterators
|
// Set @@toStringTag to native iterators
|
||||||
_setToStringTag(IteratorPrototype, TAG, true);
|
_setToStringTag(IteratorPrototype, TAG, true);
|
||||||
// fix for some old engines
|
// fix for some old engines
|
||||||
if (!_library && typeof IteratorPrototype[ITERATOR$2] != 'function') _hide(IteratorPrototype, ITERATOR$2, returnThis);
|
if (!_library && !_has(IteratorPrototype, ITERATOR$2)) _hide(IteratorPrototype, ITERATOR$2, returnThis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fix Array#{values, @@iterator}.name in V8 / FF
|
// fix Array#{values, @@iterator}.name in V8 / FF
|
||||||
@ -2503,11 +2496,9 @@ function set(target, propertyKey, V /* , receiver */) {
|
|||||||
}
|
}
|
||||||
if (_has(ownDesc, 'value')) {
|
if (_has(ownDesc, 'value')) {
|
||||||
if (ownDesc.writable === false || !_isObject(receiver)) return false;
|
if (ownDesc.writable === false || !_isObject(receiver)) return false;
|
||||||
if (existingDescriptor = _objectGopd.f(receiver, propertyKey)) {
|
existingDescriptor = _objectGopd.f(receiver, propertyKey) || _propertyDesc(0);
|
||||||
if (existingDescriptor.get || existingDescriptor.set || existingDescriptor.writable === false) return false;
|
existingDescriptor.value = V;
|
||||||
existingDescriptor.value = V;
|
_objectDp.f(receiver, propertyKey, existingDescriptor);
|
||||||
_objectDp.f(receiver, propertyKey, existingDescriptor);
|
|
||||||
} else _objectDp.f(receiver, propertyKey, _propertyDesc(0, V));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return ownDesc.set === undefined ? false : (ownDesc.set.call(receiver, V), true);
|
return ownDesc.set === undefined ? false : (ownDesc.set.call(receiver, V), true);
|
||||||
@ -2652,8 +2643,7 @@ var _microtask = function () {
|
|||||||
};
|
};
|
||||||
// environments with maybe non-completely correct, but existent Promise
|
// environments with maybe non-completely correct, but existent Promise
|
||||||
} else if (Promise$1 && Promise$1.resolve) {
|
} else if (Promise$1 && Promise$1.resolve) {
|
||||||
// Promise.resolve without an argument throws an error in LG WebOS 2
|
var promise = Promise$1.resolve();
|
||||||
var promise = Promise$1.resolve(undefined);
|
|
||||||
notify = function () {
|
notify = function () {
|
||||||
promise.then(flush);
|
promise.then(flush);
|
||||||
};
|
};
|
||||||
@ -2710,10 +2700,6 @@ var _perform = function (exec) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var navigator$1 = _global.navigator;
|
|
||||||
|
|
||||||
var _userAgent = navigator$1 && navigator$1.userAgent || '';
|
|
||||||
|
|
||||||
var _promiseResolve = function (C, x) {
|
var _promiseResolve = function (C, x) {
|
||||||
_anObject(C);
|
_anObject(C);
|
||||||
if (_isObject(x) && x.constructor === C) return x;
|
if (_isObject(x) && x.constructor === C) return x;
|
||||||
@ -2728,12 +2714,9 @@ var microtask = _microtask();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var PROMISE = 'Promise';
|
var PROMISE = 'Promise';
|
||||||
var TypeError$1 = _global.TypeError;
|
var TypeError$1 = _global.TypeError;
|
||||||
var process$2 = _global.process;
|
var process$2 = _global.process;
|
||||||
var versions = process$2 && process$2.versions;
|
|
||||||
var v8 = versions && versions.v8 || '';
|
|
||||||
var $Promise = _global[PROMISE];
|
var $Promise = _global[PROMISE];
|
||||||
var isNode$1 = _classof(process$2) == 'process';
|
var isNode$1 = _classof(process$2) == 'process';
|
||||||
var empty = function () { /* empty */ };
|
var empty = function () { /* empty */ };
|
||||||
@ -2748,13 +2731,7 @@ var USE_NATIVE = !!function () {
|
|||||||
exec(empty, empty);
|
exec(empty, empty);
|
||||||
};
|
};
|
||||||
// unhandled rejections tracking support, NodeJS Promise without it fails @@species test
|
// unhandled rejections tracking support, NodeJS Promise without it fails @@species test
|
||||||
return (isNode$1 || typeof PromiseRejectionEvent == 'function')
|
return (isNode$1 || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise;
|
||||||
&& promise.then(empty) instanceof FakePromise
|
|
||||||
// v8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables
|
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=830565
|
|
||||||
// we can't detect it synchronously, so just check versions
|
|
||||||
&& v8.indexOf('6.6') !== 0
|
|
||||||
&& _userAgent.indexOf('Chrome/66') === -1;
|
|
||||||
} catch (e) { /* empty */ }
|
} catch (e) { /* empty */ }
|
||||||
}();
|
}();
|
||||||
|
|
||||||
@ -2776,7 +2753,7 @@ var notify = function (promise, isReject) {
|
|||||||
var resolve = reaction.resolve;
|
var resolve = reaction.resolve;
|
||||||
var reject = reaction.reject;
|
var reject = reaction.reject;
|
||||||
var domain = reaction.domain;
|
var domain = reaction.domain;
|
||||||
var result, then, exited;
|
var result, then;
|
||||||
try {
|
try {
|
||||||
if (handler) {
|
if (handler) {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@ -2786,11 +2763,8 @@ var notify = function (promise, isReject) {
|
|||||||
if (handler === true) result = value;
|
if (handler === true) result = value;
|
||||||
else {
|
else {
|
||||||
if (domain) domain.enter();
|
if (domain) domain.enter();
|
||||||
result = handler(value); // may throw
|
result = handler(value);
|
||||||
if (domain) {
|
if (domain) domain.exit();
|
||||||
domain.exit();
|
|
||||||
exited = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (result === reaction.promise) {
|
if (result === reaction.promise) {
|
||||||
reject(TypeError$1('Promise-chain cycle'));
|
reject(TypeError$1('Promise-chain cycle'));
|
||||||
@ -2799,7 +2773,6 @@ var notify = function (promise, isReject) {
|
|||||||
} else resolve(result);
|
} else resolve(result);
|
||||||
} else reject(value);
|
} else reject(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (domain && !exited) domain.exit();
|
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -4182,6 +4155,10 @@ var _stringPad = function (that, maxLength, fillString, left) {
|
|||||||
return left ? stringFiller + S : S + stringFiller;
|
return left ? stringFiller + S : S + stringFiller;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var navigator$1 = _global.navigator;
|
||||||
|
|
||||||
|
var _userAgent = navigator$1 && navigator$1.userAgent || '';
|
||||||
|
|
||||||
// https://github.com/tc39/proposal-string-pad-start-end
|
// https://github.com/tc39/proposal-string-pad-start-end
|
||||||
|
|
||||||
|
|
||||||
@ -5813,7 +5790,7 @@ var Storage = function () {
|
|||||||
createClass(Storage, [{
|
createClass(Storage, [{
|
||||||
key: 'get',
|
key: 'get',
|
||||||
value: function get(key) {
|
value: function get(key) {
|
||||||
if (!Storage.supported) {
|
if (!Storage.supported || !this.enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5932,7 +5909,7 @@ var utils = {
|
|||||||
return this.instanceof(input, Event);
|
return this.instanceof(input, Event);
|
||||||
},
|
},
|
||||||
cue: function cue(input) {
|
cue: function cue(input) {
|
||||||
return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue);
|
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
|
||||||
},
|
},
|
||||||
track: function track(input) {
|
track: function track(input) {
|
||||||
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
|
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
|
||||||
@ -6043,26 +6020,25 @@ var utils = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix = 'cache-';
|
var prefix = 'cache';
|
||||||
var hasId = utils.is.string(id);
|
var hasId = utils.is.string(id);
|
||||||
var isCached = false;
|
var isCached = false;
|
||||||
|
|
||||||
var exists = function exists() {
|
var exists = function exists() {
|
||||||
return document.querySelectorAll('#' + id).length;
|
return document.getElementById(id) !== null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function injectSprite(data) {
|
var update = function update(container, data) {
|
||||||
|
container.innerHTML = data;
|
||||||
|
|
||||||
// Check again incase of race condition
|
// Check again incase of race condition
|
||||||
if (hasId && exists()) {
|
if (hasId && exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject content
|
|
||||||
this.innerHTML = data;
|
|
||||||
|
|
||||||
// Inject the SVG to the body
|
// Inject the SVG to the body
|
||||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
document.body.insertAdjacentElement('afterbegin', container);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Only load once if ID set
|
// Only load once if ID set
|
||||||
if (!hasId || !exists()) {
|
if (!hasId || !exists()) {
|
||||||
@ -6078,13 +6054,12 @@ var utils = {
|
|||||||
|
|
||||||
// Check in cache
|
// Check in cache
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
var cached = window.localStorage.getItem(prefix + id);
|
var cached = window.localStorage.getItem(prefix + '-' + id);
|
||||||
isCached = cached !== null;
|
isCached = cached !== null;
|
||||||
|
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
var data = JSON.parse(cached);
|
var data = JSON.parse(cached);
|
||||||
injectSprite.call(container, data.content);
|
update(container, data.content);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6095,12 +6070,12 @@ var utils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
window.localStorage.setItem(prefix + id, JSON.stringify({
|
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
|
||||||
content: result
|
content: result
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
injectSprite.call(container, result);
|
update(container, result);
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6681,6 +6656,14 @@ var utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Get a nested value in an object
|
||||||
|
getDeep: function getDeep(object, path) {
|
||||||
|
return path.split('.').reduce(function (obj, key) {
|
||||||
|
return obj && obj[key];
|
||||||
|
}, object);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
// Get the closest value in an array
|
// Get the closest value in an array
|
||||||
closest: function closest(array, value) {
|
closest: function closest(array, value) {
|
||||||
if (!utils.is.array(array) || !array.length) {
|
if (!utils.is.array(array) || !array.length) {
|
||||||
@ -7100,6 +7083,13 @@ var html5 = {
|
|||||||
|
|
||||||
player.media.src = supported[0].getAttribute('src');
|
player.media.src = supported[0].getAttribute('src');
|
||||||
|
|
||||||
|
// Restore time
|
||||||
|
var onLoadedMetaData = function onLoadedMetaData() {
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
player.off('loadedmetadata', onLoadedMetaData);
|
||||||
|
};
|
||||||
|
player.on('loadedmetadata', onLoadedMetaData);
|
||||||
|
|
||||||
// Load new source
|
// Load new source
|
||||||
player.media.load();
|
player.media.load();
|
||||||
|
|
||||||
@ -7108,9 +7098,6 @@ var html5 = {
|
|||||||
player.play();
|
player.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore time
|
|
||||||
player.currentTime = currentTime;
|
|
||||||
|
|
||||||
// Trigger change event
|
// Trigger change event
|
||||||
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
quality: input
|
quality: input
|
||||||
@ -7152,11 +7139,15 @@ var i18n = {
|
|||||||
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
|
if (utils.is.empty(key) || utils.is.empty(config)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var string = config.i18n[key];
|
var string = utils.getDeep(config.i18n, key);
|
||||||
|
|
||||||
|
if (utils.is.empty(string)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
var replace = {
|
var replace = {
|
||||||
'{seektime}': config.seekTime,
|
'{seektime}': config.seekTime,
|
||||||
@ -7428,30 +7419,10 @@ var controls = {
|
|||||||
this.elements.buttons[type] = button;
|
this.elements.buttons[type] = button;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* // Toggle aria-pressed state on a toggle button
|
// Toggle classname when pressed property is set
|
||||||
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
|
|
||||||
toggleState(element, input) {
|
|
||||||
// If multiple elements passed
|
|
||||||
if (utils.is.array(element) || utils.is.nodeList(element)) {
|
|
||||||
Array.from(element).forEach(target => utils.toggleState(target, input));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Bail if no target
|
|
||||||
if (!utils.is.element(element)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get state
|
|
||||||
const pressed = element.classList.contains(this.config.className;
|
|
||||||
const state = utils.is.boolean(input) ? input : !pressed;
|
|
||||||
// Set the attribute on target
|
|
||||||
element.setAttribute('aria-pressed', state);
|
|
||||||
}, */
|
|
||||||
|
|
||||||
var className = this.config.classNames.controlPressed;
|
var className = this.config.classNames.controlPressed;
|
||||||
|
|
||||||
Object.defineProperty(button, 'pressed', {
|
Object.defineProperty(button, 'pressed', {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return utils.hasClass(button, className);
|
return utils.hasClass(button, className);
|
||||||
},
|
},
|
||||||
@ -7468,13 +7439,6 @@ var controls = {
|
|||||||
|
|
||||||
// Create an <input type='range'>
|
// Create an <input type='range'>
|
||||||
createRange: function createRange(type, attributes) {
|
createRange: function createRange(type, attributes) {
|
||||||
// Seek label
|
|
||||||
var label = utils.createElement('label', {
|
|
||||||
for: attributes.id,
|
|
||||||
id: attributes.id + '-label',
|
|
||||||
class: this.config.classNames.hidden
|
|
||||||
}, i18n.get(type, this.config));
|
|
||||||
|
|
||||||
// Seek input
|
// Seek input
|
||||||
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
||||||
type: 'range',
|
type: 'range',
|
||||||
@ -7485,7 +7449,7 @@ var controls = {
|
|||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||||
role: 'slider',
|
role: 'slider',
|
||||||
'aria-labelledby': attributes.id + '-label',
|
'aria-label': i18n.get(type, this.config),
|
||||||
'aria-valuemin': 0,
|
'aria-valuemin': 0,
|
||||||
'aria-valuemax': 100,
|
'aria-valuemax': 100,
|
||||||
'aria-valuenow': 0
|
'aria-valuenow': 0
|
||||||
@ -7496,10 +7460,7 @@ var controls = {
|
|||||||
// Set the fill for webkit now
|
// Set the fill for webkit now
|
||||||
controls.updateRangeFill.call(this, input);
|
controls.updateRangeFill.call(this, input);
|
||||||
|
|
||||||
return {
|
return input;
|
||||||
label: label,
|
|
||||||
input: input
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -7861,27 +7822,7 @@ var controls = {
|
|||||||
|
|
||||||
// Get the badge HTML for HD, 4K etc
|
// Get the badge HTML for HD, 4K etc
|
||||||
var getBadge = function getBadge(quality) {
|
var getBadge = function getBadge(quality) {
|
||||||
var label = '';
|
var label = i18n.get('qualityBadge.' + quality, _this3.config);
|
||||||
|
|
||||||
switch (quality) {
|
|
||||||
case 2160:
|
|
||||||
label = '4K';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1440:
|
|
||||||
case 1080:
|
|
||||||
case 720:
|
|
||||||
label = 'HD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 576:
|
|
||||||
case 480:
|
|
||||||
label = 'SD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label.length) {
|
if (!label.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -7904,7 +7845,6 @@ var controls = {
|
|||||||
|
|
||||||
|
|
||||||
// Translate a value into a nice label
|
// Translate a value into a nice label
|
||||||
// TODO: Localisation
|
|
||||||
getLabel: function getLabel(setting, value) {
|
getLabel: function getLabel(setting, value) {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'speed':
|
case 'speed':
|
||||||
@ -7912,7 +7852,13 @@ var controls = {
|
|||||||
|
|
||||||
case 'quality':
|
case 'quality':
|
||||||
if (utils.is.number(value)) {
|
if (utils.is.number(value)) {
|
||||||
return value + 'p';
|
var label = i18n.get('qualityLabel.' + value, this.config);
|
||||||
|
|
||||||
|
if (!label.length) {
|
||||||
|
return value + 'p';
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.toTitleCase(value);
|
return utils.toTitleCase(value);
|
||||||
@ -8072,12 +8018,7 @@ var controls = {
|
|||||||
|
|
||||||
// Generate options
|
// Generate options
|
||||||
tracks.forEach(function (track) {
|
tracks.forEach(function (track) {
|
||||||
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.captions.language.toLowerCase());
|
controls.createMenuItem.call(_this4, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this4, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this4.language);
|
||||||
});
|
|
||||||
|
|
||||||
// Store reference
|
|
||||||
this.options.captions = tracks.map(function (track) {
|
|
||||||
return track.language;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
@ -8346,11 +8287,9 @@ var controls = {
|
|||||||
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
|
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
|
||||||
|
|
||||||
// Seek range slider
|
// Seek range slider
|
||||||
var seek = controls.createRange.call(this, 'seek', {
|
progress.appendChild(controls.createRange.call(this, 'seek', {
|
||||||
id: 'plyr-seek-' + data.id
|
id: 'plyr-seek-' + data.id
|
||||||
});
|
}));
|
||||||
progress.appendChild(seek.label);
|
|
||||||
progress.appendChild(seek.input);
|
|
||||||
|
|
||||||
// Buffer progress
|
// Buffer progress
|
||||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
||||||
@ -8400,11 +8339,9 @@ var controls = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create the volume range slider
|
// Create the volume range slider
|
||||||
var range = controls.createRange.call(this, 'volume', utils.extend(attributes, {
|
volume.appendChild(controls.createRange.call(this, 'volume', utils.extend(attributes, {
|
||||||
id: 'plyr-volume-' + data.id
|
id: 'plyr-volume-' + data.id
|
||||||
}));
|
})));
|
||||||
volume.appendChild(range.label);
|
|
||||||
volume.appendChild(range.input);
|
|
||||||
|
|
||||||
this.elements.volume = volume;
|
this.elements.volume = volume;
|
||||||
|
|
||||||
@ -8680,28 +8617,6 @@ var captions = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default language if not set
|
|
||||||
var stored = this.storage.get('language');
|
|
||||||
|
|
||||||
if (!utils.is.empty(stored)) {
|
|
||||||
this.captions.language = stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utils.is.empty(this.captions.language)) {
|
|
||||||
this.captions.language = this.config.captions.language.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set captions enabled state if not set
|
|
||||||
if (!utils.is.boolean(this.captions.active)) {
|
|
||||||
var active = this.storage.get('captions');
|
|
||||||
|
|
||||||
if (utils.is.boolean(active)) {
|
|
||||||
this.captions.active = active;
|
|
||||||
} else {
|
|
||||||
this.captions.active = this.config.captions.active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only Vimeo and HTML5 video supported at this point
|
// Only Vimeo and HTML5 video supported at this point
|
||||||
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
|
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
|
||||||
// Clear menu and hide
|
// Clear menu and hide
|
||||||
@ -8719,17 +8634,6 @@ var captions = {
|
|||||||
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the class hook
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
|
||||||
|
|
||||||
// Get tracks
|
|
||||||
var tracks = captions.getTracks.call(this);
|
|
||||||
|
|
||||||
// If no caption file exists, hide container for caption text
|
|
||||||
if (utils.is.empty(tracks)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get browser info
|
// Get browser info
|
||||||
var browser = utils.getBrowser();
|
var browser = utils.getBrowser();
|
||||||
|
|
||||||
@ -8752,14 +8656,52 @@ var captions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set language
|
// Try to load the value from storage
|
||||||
captions.setLanguage.call(this);
|
var active = this.storage.get('captions');
|
||||||
|
|
||||||
// Enable UI
|
// Otherwise fall back to the default config
|
||||||
captions.show.call(this);
|
if (!utils.is.boolean(active)) {
|
||||||
|
active = this.config.captions.active;
|
||||||
|
}
|
||||||
|
|
||||||
// Set available languages in list
|
// Set toggled state
|
||||||
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
this.toggleCaptions(active);
|
||||||
|
|
||||||
|
// Watch changes to textTracks and update captions menu
|
||||||
|
if (this.config.captions.update) {
|
||||||
|
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
||||||
|
setTimeout(captions.update.bind(this), 0);
|
||||||
|
},
|
||||||
|
update: function update() {
|
||||||
|
// Update tracks
|
||||||
|
var tracks = captions.getTracks.call(this);
|
||||||
|
this.options.captions = tracks.map(function (_ref) {
|
||||||
|
var language = _ref.language;
|
||||||
|
return language;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set language if it hasn't been set already
|
||||||
|
if (!this.language) {
|
||||||
|
var language = this.config.captions.language;
|
||||||
|
|
||||||
|
if (language === 'auto') {
|
||||||
|
var _split = (navigator.language || navigator.userLanguage).split('-');
|
||||||
|
|
||||||
|
var _split2 = slicedToArray(_split, 1);
|
||||||
|
|
||||||
|
language = _split2[0];
|
||||||
|
}
|
||||||
|
this.language = this.storage.get('language') || (language || '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle the class hooks
|
||||||
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||||
|
|
||||||
|
// Update available languages in list
|
||||||
|
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -8920,25 +8862,6 @@ var captions = {
|
|||||||
} else {
|
} else {
|
||||||
this.debug.warn('No captions element to render to');
|
this.debug.warn('No captions element to render to');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// Display captions container and button (for initialization)
|
|
||||||
show: function show() {
|
|
||||||
// Try to load the value from storage
|
|
||||||
var active = this.storage.get('captions');
|
|
||||||
|
|
||||||
// Otherwise fall back to the default config
|
|
||||||
if (!utils.is.boolean(active)) {
|
|
||||||
active = this.config.captions.active;
|
|
||||||
} else {
|
|
||||||
this.captions.active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true);
|
|
||||||
this.elements.buttons.captions.pressed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9044,7 +8967,7 @@ var defaults$1 = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.3.10/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -9083,7 +9006,10 @@ var defaults$1 = {
|
|||||||
// Captions settings
|
// Captions settings
|
||||||
captions: {
|
captions: {
|
||||||
active: false,
|
active: false,
|
||||||
language: (navigator.language || navigator.userLanguage).split('-')[0]
|
language: 'auto',
|
||||||
|
// Listen to new tracks added after Plyr is initialized.
|
||||||
|
// This is needed for streaming captions, but may result in unselectable options
|
||||||
|
update: false
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fullscreen settings
|
// Fullscreen settings
|
||||||
@ -9140,7 +9066,15 @@ var defaults$1 = {
|
|||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
advertisement: 'Ad'
|
advertisement: 'Ad',
|
||||||
|
qualityBadge: {
|
||||||
|
2160: '4K',
|
||||||
|
1440: 'HD',
|
||||||
|
1080: 'HD',
|
||||||
|
720: 'HD',
|
||||||
|
576: 'SD',
|
||||||
|
480: 'SD'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -9228,9 +9162,8 @@ var defaults$1 = {
|
|||||||
display: {
|
display: {
|
||||||
currentTime: '.plyr__time--current',
|
currentTime: '.plyr__time--current',
|
||||||
duration: '.plyr__time--duration',
|
duration: '.plyr__time--duration',
|
||||||
buffer: '.plyr__progress--buffer',
|
buffer: '.plyr__progress__buffer',
|
||||||
played: '.plyr__progress--played',
|
loop: '.plyr__progress__loop', // Used later
|
||||||
loop: '.plyr__progress--loop',
|
|
||||||
volume: '.plyr__volume--display'
|
volume: '.plyr__volume--display'
|
||||||
},
|
},
|
||||||
progress: '.plyr__progress',
|
progress: '.plyr__progress',
|
||||||
@ -9600,8 +9533,10 @@ var ui = {
|
|||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Captions
|
// Setup captions for HTML5
|
||||||
captions.setup.call(this);
|
if (this.isHTML5) {
|
||||||
|
captions.setup.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset volume
|
// Reset volume
|
||||||
this.volume = null;
|
this.volume = null;
|
||||||
@ -9654,6 +9589,12 @@ var ui = {
|
|||||||
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
||||||
ui.setPoster.call(this, this.poster);
|
ui.setPoster.call(this, this.poster);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually set the duration if user has overridden it.
|
||||||
|
// The event listeners for it doesn't get called if preload is disabled (#701)
|
||||||
|
if (this.config.duration) {
|
||||||
|
controls.durationUpdate.call(this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -9847,7 +9788,7 @@ var Listeners = function () {
|
|||||||
// and if the focused element is not editable (e.g. text input)
|
// and if the focused element is not editable (e.g. text input)
|
||||||
// and any that accept key input http://webaim.org/techniques/keyboard/
|
// and any that accept key input http://webaim.org/techniques/keyboard/
|
||||||
var focused = utils.getFocusElement();
|
var focused = utils.getFocusElement();
|
||||||
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
|
if (utils.is.element(focused) && focused !== this.player.elements.inputs.seek && utils.matches(focused, this.player.config.selectors.editable)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10341,6 +10282,12 @@ var Listeners = function () {
|
|||||||
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
|
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
|
||||||
var seek = event.currentTarget;
|
var seek = event.currentTarget;
|
||||||
|
|
||||||
|
var code = event.keyCode ? event.keyCode : event.which;
|
||||||
|
var eventType = event.type;
|
||||||
|
|
||||||
|
if ((eventType === 'keydown' || eventType === 'keyup') && code !== 39 && code !== 37) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Was playing before?
|
// Was playing before?
|
||||||
var play = seek.hasAttribute('play-on-seeked');
|
var play = seek.hasAttribute('play-on-seeked');
|
||||||
|
|
||||||
@ -12715,24 +12662,19 @@ var Plyr = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
// If the method is called without parameter, toggle based on current value
|
||||||
var show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
var active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
||||||
|
|
||||||
// Nothing to change...
|
|
||||||
if (this.captions.active === show) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set global
|
|
||||||
this.captions.active = show;
|
|
||||||
|
|
||||||
// Toggle state
|
// Toggle state
|
||||||
this.elements.buttons.captions.pressed = this.captions.active;
|
this.elements.buttons.captions.pressed = active;
|
||||||
|
|
||||||
// Add class hook
|
// Add class hook
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active);
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active);
|
||||||
|
|
||||||
// Trigger an event
|
// Update state and trigger event
|
||||||
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
if (active !== this.captions.active) {
|
||||||
|
this.captions.active = active;
|
||||||
|
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13020,21 +12962,16 @@ var Plyr = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'currentTime',
|
key: 'currentTime',
|
||||||
set: function set(input) {
|
set: function set(input) {
|
||||||
var targetTime = 0;
|
// Bail if media duration isn't available yet
|
||||||
|
if (!this.duration) {
|
||||||
if (utils.is.number(input)) {
|
return;
|
||||||
targetTime = input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalise targetTime
|
// Validate input
|
||||||
if (targetTime < 0) {
|
var inputIsValid = utils.is.number(input) && input > 0;
|
||||||
targetTime = 0;
|
|
||||||
} else if (targetTime > this.duration) {
|
|
||||||
targetTime = this.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set
|
// Set
|
||||||
this.media.currentTime = targetTime;
|
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
|
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
|
||||||
@ -13093,11 +13030,11 @@ var Plyr = function () {
|
|||||||
// Faux duration set via config
|
// Faux duration set via config
|
||||||
var fauxDuration = parseFloat(this.config.duration);
|
var fauxDuration = parseFloat(this.config.duration);
|
||||||
|
|
||||||
// True duration
|
// Media duration can be NaN before the media has loaded
|
||||||
var realDuration = this.media ? Number(this.media.duration) : 0;
|
var duration = (this.media || {}).duration || 0;
|
||||||
|
|
||||||
// If custom duration is funky, use regular duration
|
// If config duration is funky, use regular duration
|
||||||
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
return fauxDuration || duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13269,7 +13206,7 @@ var Plyr = function () {
|
|||||||
quality = Number(input);
|
quality = Number(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils.is.number(quality) || quality === 0) {
|
if (!utils.is.number(quality)) {
|
||||||
quality = this.storage.get('quality');
|
quality = this.storage.get('quality');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
dist/plyr.polyfilled.js.map
vendored
2
dist/plyr.polyfilled.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
2
dist/plyr.polyfilled.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js.map
vendored
2
dist/plyr.polyfilled.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -226,9 +226,14 @@ gulp.task('watch', () => {
|
|||||||
gulp.watch(paths.demo.src.sass, tasks.sass);
|
gulp.watch(paths.demo.src.sass, tasks.sass);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Build distribution
|
||||||
|
gulp.task('build', () => {
|
||||||
|
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite);
|
||||||
|
});
|
||||||
|
|
||||||
// Default gulp task
|
// Default gulp task
|
||||||
gulp.task('default', () => {
|
gulp.task('default', () => {
|
||||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'watch');
|
run('build', 'watch');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish a version to CDN and demo
|
// Publish a version to CDN and demo
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.3.8",
|
"version": "3.3.10",
|
||||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"main": "./dist/plyr.js",
|
"main": "./dist/plyr.js",
|
||||||
@ -65,6 +65,8 @@
|
|||||||
"doc": "readme.md"
|
"doc": "readme.md"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "gulp build",
|
||||||
|
"lint": "eslint src/js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
|
10
readme.md
10
readme.md
@ -128,13 +128,13 @@ See [initialising](#initialising) for more information on advanced setups.
|
|||||||
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.3.8/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.3.10/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
...or...
|
...or...
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.3.8/plyr.polyfilled.js"></script>
|
<script src="https://cdn.plyr.io/3.3.10/plyr.polyfilled.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
### CSS
|
### CSS
|
||||||
@ -148,13 +148,13 @@ Include the `plyr.css` stylsheet into your `<head>`
|
|||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.8/plyr.css">
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.10/plyr.css">
|
||||||
```
|
```
|
||||||
|
|
||||||
### SVG Sprite
|
### SVG Sprite
|
||||||
|
|
||||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.3.8/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.3.10/plyr.svg`.
|
||||||
|
|
||||||
## Ads
|
## Ads
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
||||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||||
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
|
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||||
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||||
|
@ -16,28 +16,6 @@ const captions = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default language if not set
|
|
||||||
const stored = this.storage.get('language');
|
|
||||||
|
|
||||||
if (!utils.is.empty(stored)) {
|
|
||||||
this.captions.language = stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utils.is.empty(this.captions.language)) {
|
|
||||||
this.captions.language = this.config.captions.language.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set captions enabled state if not set
|
|
||||||
if (!utils.is.boolean(this.captions.active)) {
|
|
||||||
const active = this.storage.get('captions');
|
|
||||||
|
|
||||||
if (utils.is.boolean(active)) {
|
|
||||||
this.captions.active = active;
|
|
||||||
} else {
|
|
||||||
this.captions.active = this.config.captions.active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only Vimeo and HTML5 video supported at this point
|
// Only Vimeo and HTML5 video supported at this point
|
||||||
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
||||||
// Clear menu and hide
|
// Clear menu and hide
|
||||||
@ -55,17 +33,6 @@ const captions = {
|
|||||||
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the class hook
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
|
||||||
|
|
||||||
// Get tracks
|
|
||||||
const tracks = captions.getTracks.call(this);
|
|
||||||
|
|
||||||
// If no caption file exists, hide container for caption text
|
|
||||||
if (utils.is.empty(tracks)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get browser info
|
// Get browser info
|
||||||
const browser = utils.getBrowser();
|
const browser = utils.getBrowser();
|
||||||
|
|
||||||
@ -94,14 +61,45 @@ const captions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set language
|
// Try to load the value from storage
|
||||||
captions.setLanguage.call(this);
|
let active = this.storage.get('captions');
|
||||||
|
|
||||||
// Enable UI
|
// Otherwise fall back to the default config
|
||||||
captions.show.call(this);
|
if (!utils.is.boolean(active)) {
|
||||||
|
({ active } = this.config.captions);
|
||||||
|
}
|
||||||
|
|
||||||
// Set available languages in list
|
// Set toggled state
|
||||||
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
this.toggleCaptions(active);
|
||||||
|
|
||||||
|
// Watch changes to textTracks and update captions menu
|
||||||
|
if (this.config.captions.update) {
|
||||||
|
utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
||||||
|
setTimeout(captions.update.bind(this), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
update() {
|
||||||
|
// Update tracks
|
||||||
|
const tracks = captions.getTracks.call(this);
|
||||||
|
this.options.captions = tracks.map(({ language }) => language);
|
||||||
|
|
||||||
|
// Set language if it hasn't been set already
|
||||||
|
if (!this.language) {
|
||||||
|
let { language } = this.config.captions;
|
||||||
|
if (language === 'auto') {
|
||||||
|
[language] = (navigator.language || navigator.userLanguage).split('-');
|
||||||
|
}
|
||||||
|
this.language = this.storage.get('language') || (language || '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle the class hooks
|
||||||
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||||
|
|
||||||
|
// Update available languages in list
|
||||||
|
if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -247,24 +245,6 @@ const captions = {
|
|||||||
this.debug.warn('No captions element to render to');
|
this.debug.warn('No captions element to render to');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Display captions container and button (for initialization)
|
|
||||||
show() {
|
|
||||||
// Try to load the value from storage
|
|
||||||
let active = this.storage.get('captions');
|
|
||||||
|
|
||||||
// Otherwise fall back to the default config
|
|
||||||
if (!utils.is.boolean(active)) {
|
|
||||||
({ active } = this.config.captions);
|
|
||||||
} else {
|
|
||||||
this.captions.active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true);
|
|
||||||
this.elements.buttons.captions.pressed = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default captions;
|
export default captions;
|
||||||
|
36
src/js/controls.js
vendored
36
src/js/controls.js
vendored
@ -659,27 +659,7 @@ const controls = {
|
|||||||
|
|
||||||
// Get the badge HTML for HD, 4K etc
|
// Get the badge HTML for HD, 4K etc
|
||||||
const getBadge = quality => {
|
const getBadge = quality => {
|
||||||
let label = '';
|
const label = i18n.get(`qualityBadge.${quality}`, this.config);
|
||||||
|
|
||||||
switch (quality) {
|
|
||||||
case 2160:
|
|
||||||
label = '4K';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1440:
|
|
||||||
case 1080:
|
|
||||||
case 720:
|
|
||||||
label = 'HD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 576:
|
|
||||||
case 480:
|
|
||||||
label = 'SD';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label.length) {
|
if (!label.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -703,7 +683,6 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Translate a value into a nice label
|
// Translate a value into a nice label
|
||||||
// TODO: Localisation
|
|
||||||
getLabel(setting, value) {
|
getLabel(setting, value) {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'speed':
|
case 'speed':
|
||||||
@ -711,7 +690,13 @@ const controls = {
|
|||||||
|
|
||||||
case 'quality':
|
case 'quality':
|
||||||
if (utils.is.number(value)) {
|
if (utils.is.number(value)) {
|
||||||
return `${value}p`;
|
const label = i18n.get(`qualityLabel.${value}`, this.config);
|
||||||
|
|
||||||
|
if (!label.length) {
|
||||||
|
return `${value}p`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.toTitleCase(value);
|
return utils.toTitleCase(value);
|
||||||
@ -878,13 +863,10 @@ const controls = {
|
|||||||
'language',
|
'language',
|
||||||
track.label,
|
track.label,
|
||||||
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
|
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
|
||||||
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
track.language.toLowerCase() === this.language,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store reference
|
|
||||||
this.options.captions = tracks.map(track => track.language);
|
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.3.10/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -119,7 +119,10 @@ const defaults = {
|
|||||||
// Captions settings
|
// Captions settings
|
||||||
captions: {
|
captions: {
|
||||||
active: false,
|
active: false,
|
||||||
language: (navigator.language || navigator.userLanguage).split('-')[0],
|
language: 'auto',
|
||||||
|
// Listen to new tracks added after Plyr is initialized.
|
||||||
|
// This is needed for streaming captions, but may result in unselectable options
|
||||||
|
update: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fullscreen settings
|
// Fullscreen settings
|
||||||
@ -191,6 +194,14 @@ const defaults = {
|
|||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
advertisement: 'Ad',
|
advertisement: 'Ad',
|
||||||
|
qualityBadge: {
|
||||||
|
2160: '4K',
|
||||||
|
1440: 'HD',
|
||||||
|
1080: 'HD',
|
||||||
|
720: 'HD',
|
||||||
|
576: 'SD',
|
||||||
|
480: 'SD',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -315,9 +326,8 @@ const defaults = {
|
|||||||
display: {
|
display: {
|
||||||
currentTime: '.plyr__time--current',
|
currentTime: '.plyr__time--current',
|
||||||
duration: '.plyr__time--duration',
|
duration: '.plyr__time--duration',
|
||||||
buffer: '.plyr__progress--buffer',
|
buffer: '.plyr__progress__buffer',
|
||||||
played: '.plyr__progress--played',
|
loop: '.plyr__progress__loop', // Used later
|
||||||
loop: '.plyr__progress--loop',
|
|
||||||
volume: '.plyr__volume--display',
|
volume: '.plyr__volume--display',
|
||||||
},
|
},
|
||||||
progress: '.plyr__progress',
|
progress: '.plyr__progress',
|
||||||
|
@ -99,6 +99,13 @@ const html5 = {
|
|||||||
// Set new source
|
// Set new source
|
||||||
player.media.src = supported[0].getAttribute('src');
|
player.media.src = supported[0].getAttribute('src');
|
||||||
|
|
||||||
|
// Restore time
|
||||||
|
const onLoadedMetaData = () => {
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
player.off('loadedmetadata', onLoadedMetaData);
|
||||||
|
};
|
||||||
|
player.on('loadedmetadata', onLoadedMetaData);
|
||||||
|
|
||||||
// Load new source
|
// Load new source
|
||||||
player.media.load();
|
player.media.load();
|
||||||
|
|
||||||
@ -107,9 +114,6 @@ const html5 = {
|
|||||||
player.play();
|
player.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore time
|
|
||||||
player.currentTime = currentTime;
|
|
||||||
|
|
||||||
// Trigger change event
|
// Trigger change event
|
||||||
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
quality: input,
|
quality: input,
|
||||||
|
@ -6,11 +6,15 @@ import utils from './utils';
|
|||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
get(key = '', config = {}) {
|
get(key = '', config = {}) {
|
||||||
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
|
if (utils.is.empty(key) || utils.is.empty(config)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = config.i18n[key];
|
let string = utils.getDeep(config.i18n, key);
|
||||||
|
|
||||||
|
if (utils.is.empty(string)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const replace = {
|
const replace = {
|
||||||
'{seektime}': config.seekTime,
|
'{seektime}': config.seekTime,
|
||||||
|
@ -74,7 +74,10 @@ class Listeners {
|
|||||||
// and if the focused element is not editable (e.g. text input)
|
// and if the focused element is not editable (e.g. text input)
|
||||||
// and any that accept key input http://webaim.org/techniques/keyboard/
|
// and any that accept key input http://webaim.org/techniques/keyboard/
|
||||||
const focused = utils.getFocusElement();
|
const focused = utils.getFocusElement();
|
||||||
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
|
if (utils.is.element(focused) && (
|
||||||
|
focused !== this.player.elements.inputs.seek &&
|
||||||
|
utils.matches(focused, this.player.config.selectors.editable))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,6 +563,12 @@ class Listeners {
|
|||||||
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
||||||
const seek = event.currentTarget;
|
const seek = event.currentTarget;
|
||||||
|
|
||||||
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
|
const eventType = event.type;
|
||||||
|
|
||||||
|
if ((eventType === 'keydown' || eventType === 'keyup') && (code !== 39 && code !== 37)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Was playing before?
|
// Was playing before?
|
||||||
const play = seek.hasAttribute('play-on-seeked');
|
const play = seek.hasAttribute('play-on-seeked');
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.3.8
|
// plyr.js v3.3.10
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -432,21 +432,16 @@ class Plyr {
|
|||||||
* @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
|
* @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
|
||||||
*/
|
*/
|
||||||
set currentTime(input) {
|
set currentTime(input) {
|
||||||
let targetTime = 0;
|
// Bail if media duration isn't available yet
|
||||||
|
if (!this.duration) {
|
||||||
if (utils.is.number(input)) {
|
return;
|
||||||
targetTime = input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalise targetTime
|
// Validate input
|
||||||
if (targetTime < 0) {
|
const inputIsValid = utils.is.number(input) && input > 0;
|
||||||
targetTime = 0;
|
|
||||||
} else if (targetTime > this.duration) {
|
|
||||||
targetTime = this.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set
|
// Set
|
||||||
this.media.currentTime = targetTime;
|
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
||||||
@ -494,11 +489,11 @@ class Plyr {
|
|||||||
// Faux duration set via config
|
// Faux duration set via config
|
||||||
const fauxDuration = parseFloat(this.config.duration);
|
const fauxDuration = parseFloat(this.config.duration);
|
||||||
|
|
||||||
// True duration
|
// Media duration can be NaN before the media has loaded
|
||||||
const realDuration = this.media ? Number(this.media.duration) : 0;
|
const duration = (this.media || {}).duration || 0;
|
||||||
|
|
||||||
// If custom duration is funky, use regular duration
|
// If config duration is funky, use regular duration
|
||||||
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
return fauxDuration || duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -680,7 +675,7 @@ class Plyr {
|
|||||||
quality = Number(input);
|
quality = Number(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils.is.number(quality) || quality === 0) {
|
if (!utils.is.number(quality)) {
|
||||||
quality = this.storage.get('quality');
|
quality = this.storage.get('quality');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -843,24 +838,19 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
// If the method is called without parameter, toggle based on current value
|
||||||
const show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
const active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
||||||
|
|
||||||
// Nothing to change...
|
|
||||||
if (this.captions.active === show) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set global
|
|
||||||
this.captions.active = show;
|
|
||||||
|
|
||||||
// Toggle state
|
// Toggle state
|
||||||
this.elements.buttons.captions.pressed = this.captions.active;
|
this.elements.buttons.captions.pressed = active;
|
||||||
|
|
||||||
// Add class hook
|
// Add class hook
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active);
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active);
|
||||||
|
|
||||||
// Trigger an event
|
// Update state and trigger event
|
||||||
|
if (active !== this.captions.active) {
|
||||||
|
this.captions.active = active;
|
||||||
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.3.8
|
// plyr.js v3.3.10
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -31,7 +31,7 @@ class Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
if (!Storage.supported) {
|
if (!Storage.supported || !this.enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/js/ui.js
12
src/js/ui.js
@ -55,8 +55,10 @@ const ui = {
|
|||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Captions
|
// Setup captions for HTML5
|
||||||
captions.setup.call(this);
|
if (this.isHTML5) {
|
||||||
|
captions.setup.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset volume
|
// Reset volume
|
||||||
this.volume = null;
|
this.volume = null;
|
||||||
@ -109,6 +111,12 @@ const ui = {
|
|||||||
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
|
||||||
ui.setPoster.call(this, this.poster);
|
ui.setPoster.call(this, this.poster);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually set the duration if user has overridden it.
|
||||||
|
// The event listeners for it doesn't get called if preload is disabled (#701)
|
||||||
|
if (this.config.duration) {
|
||||||
|
controls.durationUpdate.call(this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup aria attribute for play and iframe title
|
// Setup aria attribute for play and iframe title
|
||||||
|
@ -44,7 +44,7 @@ const utils = {
|
|||||||
return this.instanceof(input, Event);
|
return this.instanceof(input, Event);
|
||||||
},
|
},
|
||||||
cue(input) {
|
cue(input) {
|
||||||
return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue);
|
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
|
||||||
},
|
},
|
||||||
track(input) {
|
track(input) {
|
||||||
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
|
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
|
||||||
@ -151,24 +151,23 @@ const utils = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = 'cache-';
|
const prefix = 'cache';
|
||||||
const hasId = utils.is.string(id);
|
const hasId = utils.is.string(id);
|
||||||
let isCached = false;
|
let isCached = false;
|
||||||
|
|
||||||
const exists = () => document.querySelectorAll(`#${id}`).length;
|
const exists = () => document.getElementById(id) !== null;
|
||||||
|
|
||||||
|
const update = (container, data) => {
|
||||||
|
container.innerHTML = data;
|
||||||
|
|
||||||
function injectSprite(data) {
|
|
||||||
// Check again incase of race condition
|
// Check again incase of race condition
|
||||||
if (hasId && exists()) {
|
if (hasId && exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject content
|
|
||||||
this.innerHTML = data;
|
|
||||||
|
|
||||||
// Inject the SVG to the body
|
// Inject the SVG to the body
|
||||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
document.body.insertAdjacentElement('afterbegin', container);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Only load once if ID set
|
// Only load once if ID set
|
||||||
if (!hasId || !exists()) {
|
if (!hasId || !exists()) {
|
||||||
@ -184,13 +183,12 @@ const utils = {
|
|||||||
|
|
||||||
// Check in cache
|
// Check in cache
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
const cached = window.localStorage.getItem(prefix + id);
|
const cached = window.localStorage.getItem(`${prefix}-${id}`);
|
||||||
isCached = cached !== null;
|
isCached = cached !== null;
|
||||||
|
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
const data = JSON.parse(cached);
|
const data = JSON.parse(cached);
|
||||||
injectSprite.call(container, data.content);
|
update(container, data.content);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,14 +202,14 @@ const utils = {
|
|||||||
|
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
prefix + id,
|
`${prefix}-${id}`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: result,
|
content: result,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
injectSprite.call(container, result);
|
update(container, result);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
@ -706,6 +704,11 @@ const utils = {
|
|||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Get a nested value in an object
|
||||||
|
getDeep(object, path) {
|
||||||
|
return path.split('.').reduce((obj, key) => obj && obj[key], object);
|
||||||
|
},
|
||||||
|
|
||||||
// Get the closest value in an array
|
// Get the closest value in an array
|
||||||
closest(array, value) {
|
closest(array, value) {
|
||||||
if (!utils.is.array(array) || !array.length) {
|
if (!utils.is.array(array) || !array.length) {
|
||||||
|
@ -5,16 +5,21 @@
|
|||||||
.plyr__progress {
|
.plyr__progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
|
||||||
margin-right: $plyr-range-thumb-height;
|
|
||||||
left: $plyr-range-thumb-height / 2;
|
left: $plyr-range-thumb-height / 2;
|
||||||
|
margin-right: $plyr-range-thumb-height;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input[type='range'],
|
||||||
|
&__buffer {
|
||||||
|
margin-left: -($plyr-range-thumb-height / 2);
|
||||||
|
margin-right: -($plyr-range-thumb-height / 2);
|
||||||
|
// Offset the range thumb in order to be able to calculate the relative progress (#954)
|
||||||
|
width: calc(100% + #{$plyr-range-thumb-height});
|
||||||
|
}
|
||||||
|
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
// Offset the range thumb in order to be able to calculate the relative progress (#954)
|
|
||||||
width: calc(100% + #{$plyr-range-thumb-height}) !important;
|
|
||||||
margin: 0 -#{$plyr-range-thumb-height / 2} !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek tooltip to show time
|
// Seek tooltip to show time
|
||||||
@ -24,18 +29,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__progress--buffer {
|
.plyr__progress__buffer {
|
||||||
-webkit-appearance: none; /* stylelint-disable-line */
|
-webkit-appearance: none; /* stylelint-disable-line */
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
height: $plyr-range-track-height;
|
height: $plyr-range-track-height;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: -($plyr-range-track-height / 2) 0 0;
|
margin-top: -($plyr-range-track-height / 2);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&::-webkit-progress-bar {
|
&::-webkit-progress-bar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -63,17 +67,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--video .plyr__progress--buffer {
|
.plyr--video .plyr__progress__buffer {
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.15);
|
box-shadow: 0 1px 1px rgba(#000, 0.15);
|
||||||
color: $plyr-video-progress-buffered-bg;
|
color: $plyr-video-progress-buffered-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--audio .plyr__progress--buffer {
|
.plyr--audio .plyr__progress__buffer {
|
||||||
color: $plyr-audio-progress-buffered-bg;
|
color: $plyr-audio-progress-buffered-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
.plyr--loading .plyr__progress--buffer {
|
.plyr--loading .plyr__progress__buffer {
|
||||||
animation: plyr-progress 1s linear infinite;
|
animation: plyr-progress 1s linear infinite;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
-45deg,
|
-45deg,
|
||||||
@ -90,10 +94,10 @@
|
|||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--video.plyr--loading .plyr__progress--buffer {
|
.plyr--video.plyr--loading .plyr__progress__buffer {
|
||||||
background-color: $plyr-video-progress-buffered-bg;
|
background-color: $plyr-video-progress-buffered-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--audio.plyr--loading .plyr__progress--buffer {
|
.plyr--audio.plyr--loading .plyr__progress__buffer {
|
||||||
background-color: $plyr-audio-progress-buffered-bg;
|
background-color: $plyr-audio-progress-buffered-bg;
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,12 @@
|
|||||||
@import 'components/controls';
|
@import 'components/controls';
|
||||||
@import 'components/embed';
|
@import 'components/embed';
|
||||||
@import 'components/menus';
|
@import 'components/menus';
|
||||||
@import 'components/progress';
|
|
||||||
@import 'components/poster';
|
|
||||||
@import 'components/sliders';
|
@import 'components/sliders';
|
||||||
|
@import 'components/poster';
|
||||||
@import 'components/times';
|
@import 'components/times';
|
||||||
@import 'components/tooltips';
|
@import 'components/tooltips';
|
||||||
@import 'components/video';
|
@import 'components/video';
|
||||||
|
@import 'components/progress';
|
||||||
@import 'components/volume';
|
@import 'components/volume';
|
||||||
|
|
||||||
@import 'states/fullscreen';
|
@import 'states/fullscreen';
|
||||||
|
Reference in New Issue
Block a user