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:
Sam Potts
2018-06-09 17:03:16 +10:00
32 changed files with 771 additions and 2103 deletions

View File

@ -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 }]
}, },

View File

@ -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
View File

@ -0,0 +1,7 @@
language: node_js
node_js:
- 'lts/*'
script:
- npm run lint
- npm run build

View File

@ -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

View File

@ -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

File diff suppressed because one or more lines are too long

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

329
dist/plyr.js vendored
View File

@ -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

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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');
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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>",

View File

@ -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. |

View File

@ -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
View File

@ -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);
}, },

View File

@ -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',

View File

@ -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,

View File

@ -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,

View File

@ -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');

View File

@ -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');
}
} }
/** /**

View File

@ -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)
// ========================================================================== // ==========================================================================

View File

@ -31,7 +31,7 @@ class Storage {
} }
get(key) { get(key) {
if (!Storage.supported) { if (!Storage.supported || !this.enabled) {
return null; return null;
} }

View File

@ -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

View File

@ -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) {

View File

@ -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;
} }

View File

@ -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';

1762
yarn.lock

File diff suppressed because it is too large Load Diff