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

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, [{
key: 'get',
value: function get$$1(key) {
if (!Storage.supported) {
if (!Storage.supported || !this.enabled) {
return null;
}
@ -526,7 +526,7 @@ var utils = {
return this.instanceof(input, Event);
},
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) {
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
@ -637,26 +637,25 @@ var utils = {
return;
}
var prefix = 'cache-';
var prefix = 'cache';
var hasId = utils.is.string(id);
var isCached = false;
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
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
// 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
if (!hasId || !exists()) {
@ -672,13 +671,12 @@ var utils = {
// Check in cache
if (useStorage) {
var cached = window.localStorage.getItem(prefix + id);
var cached = window.localStorage.getItem(prefix + '-' + id);
isCached = cached !== null;
if (isCached) {
var data = JSON.parse(cached);
injectSprite.call(container, data.content);
return;
update(container, data.content);
}
}
@ -689,12 +687,12 @@ var utils = {
}
if (useStorage) {
window.localStorage.setItem(prefix + id, JSON.stringify({
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
content: result
}));
}
injectSprite.call(container, result);
update(container, result);
}).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
closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) {
@ -1694,6 +1700,13 @@ var html5 = {
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
player.media.load();
@ -1702,9 +1715,6 @@ var html5 = {
player.play();
}
// Restore time
player.currentTime = currentTime;
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input
@ -1746,11 +1756,15 @@ var i18n = {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
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 '';
}
var string = config.i18n[key];
var string = utils.getDeep(config.i18n, key);
if (utils.is.empty(string)) {
return '';
}
var replace = {
'{seektime}': config.seekTime,
@ -2001,9 +2015,6 @@ var controls = {
// Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
// Add aria attributes
attributes['aria-pressed'] = false;
} else {
button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label));
@ -2025,19 +2036,26 @@ var controls = {
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;
},
// Create an <input type='range'>
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
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
type: 'range',
@ -2048,7 +2066,7 @@ var controls = {
autocomplete: 'off',
// A11y fixes for https://github.com/sampotts/plyr/issues/905
role: 'slider',
'aria-labelledby': attributes.id + '-label',
'aria-label': i18n.get(type, this.config),
'aria-valuemin': 0,
'aria-valuemax': 100,
'aria-valuenow': 0
@ -2059,10 +2077,7 @@ var controls = {
// Set the fill for webkit now
controls.updateRangeFill.call(this, input);
return {
label: label,
input: input
};
return input;
},
@ -2185,7 +2200,7 @@ var controls = {
// Update mute state
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
var getBadge = function getBadge(quality) {
var label = '';
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;
}
var label = i18n.get('qualityBadge.' + quality, _this3.config);
if (!label.length) {
return null;
@ -2467,7 +2462,6 @@ var controls = {
// Translate a value into a nice label
// TODO: Localisation
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
@ -2475,7 +2469,13 @@ var controls = {
case 'quality':
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);
@ -2635,12 +2635,7 @@ var controls = {
// Generate options
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());
});
// Store reference
this.options.captions = tracks.map(function (track) {
return track.language;
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);
});
controls.updateSetting.call(this, type, list);
@ -2909,11 +2904,9 @@ var controls = {
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider
var seek = controls.createRange.call(this, 'seek', {
progress.appendChild(controls.createRange.call(this, 'seek', {
id: 'plyr-seek-' + data.id
});
progress.appendChild(seek.label);
progress.appendChild(seek.input);
}));
// Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer'));
@ -2963,11 +2956,9 @@ var controls = {
};
// 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
}));
volume.appendChild(range.label);
volume.appendChild(range.input);
})));
this.elements.volume = volume;
@ -3243,28 +3234,6 @@ var captions = {
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
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
// Clear menu and hide
@ -3282,17 +3251,6 @@ var captions = {
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
var browser = utils.getBrowser();
@ -3315,14 +3273,52 @@ var captions = {
});
}
// Set language
captions.setLanguage.call(this);
// Try to load the value from storage
var active = this.storage.get('captions');
// Enable UI
captions.show.call(this);
// Otherwise fall back to the default config
if (!utils.is.boolean(active)) {
active = this.config.captions.active;
}
// Set available languages in list
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
// Set toggled state
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);
}
},
@ -3483,25 +3479,6 @@ var captions = {
} else {
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)
loadSprite: true,
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)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -3646,7 +3623,10 @@ var defaults$1 = {
// Captions settings
captions: {
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
@ -3703,7 +3683,15 @@ var defaults$1 = {
reset: 'Reset',
disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad'
advertisement: 'Ad',
qualityBadge: {
2160: '4K',
1440: 'HD',
1080: 'HD',
720: 'HD',
576: 'SD',
480: 'SD'
}
},
// URLs
@ -3791,9 +3779,8 @@ var defaults$1 = {
display: {
currentTime: '.plyr__time--current',
duration: '.plyr__time--duration',
buffer: '.plyr__progress--buffer',
played: '.plyr__progress--played',
loop: '.plyr__progress--loop',
buffer: '.plyr__progress__buffer',
loop: '.plyr__progress__loop', // Used later
volume: '.plyr__volume--display'
},
progress: '.plyr__progress',
@ -4163,8 +4150,10 @@ var ui = {
// Remove native controls
ui.toggleNativeControls.call(this);
// Captions
captions.setup.call(this);
// Setup captions for HTML5
if (this.isHTML5) {
captions.setup.call(this);
}
// Reset volume
this.volume = null;
@ -4217,6 +4206,12 @@ var ui = {
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
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 any that accept key input http://webaim.org/techniques/keyboard/
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;
}
@ -4904,6 +4899,12 @@ var Listeners = function () {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
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?
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
var show = 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;
var active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
// Toggle state
utils.toggleState(this.elements.buttons.captions, this.captions.active);
this.elements.buttons.captions.pressed = active;
// 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
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
// 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');
}
}
/**
@ -7583,21 +7579,16 @@ var Plyr = function () {
}, {
key: 'currentTime',
set: function set$$1(input) {
var targetTime = 0;
if (utils.is.number(input)) {
targetTime = input;
// Bail if media duration isn't available yet
if (!this.duration) {
return;
}
// Normalise targetTime
if (targetTime < 0) {
targetTime = 0;
} else if (targetTime > this.duration) {
targetTime = this.duration;
}
// Validate input
var inputIsValid = utils.is.number(input) && input > 0;
// Set
this.media.currentTime = targetTime;
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
// Logging
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
@ -7656,11 +7647,11 @@ var Plyr = function () {
// Faux duration set via config
var fauxDuration = parseFloat(this.config.duration);
// True duration
var realDuration = this.media ? Number(this.media.duration) : 0;
// Media duration can be NaN before the media has loaded
var duration = (this.media || {}).duration || 0;
// If custom duration is funky, use regular duration
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
// If config duration is funky, use regular duration
return fauxDuration || duration;
}
/**
@ -7832,7 +7823,7 @@ var Plyr = function () {
quality = Number(input);
}
if (!utils.is.number(quality) || quality === 0) {
if (!utils.is.number(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 = 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
});
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 store = _global[SHARED] || (_global[SHARED] = {});
(module.exports = function (key, value) {
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 = function (key) {
return store[key] || (store[key] = {});
};
var shared = _shared('keys');
@ -998,7 +991,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC
var VALUES_BUG = false;
var proto = Base.prototype;
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 $anyNative = NAME == 'Array' ? proto.entries || $native : $native;
var methods, key, IteratorPrototype;
@ -1009,7 +1002,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC
// Set @@toStringTag to native iterators
_setToStringTag(IteratorPrototype, TAG, true);
// 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
@ -2503,11 +2496,9 @@ function set(target, propertyKey, V /* , receiver */) {
}
if (_has(ownDesc, 'value')) {
if (ownDesc.writable === false || !_isObject(receiver)) return false;
if (existingDescriptor = _objectGopd.f(receiver, propertyKey)) {
if (existingDescriptor.get || existingDescriptor.set || existingDescriptor.writable === false) return false;
existingDescriptor.value = V;
_objectDp.f(receiver, propertyKey, existingDescriptor);
} else _objectDp.f(receiver, propertyKey, _propertyDesc(0, V));
existingDescriptor = _objectGopd.f(receiver, propertyKey) || _propertyDesc(0);
existingDescriptor.value = V;
_objectDp.f(receiver, propertyKey, existingDescriptor);
return 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
} else if (Promise$1 && Promise$1.resolve) {
// Promise.resolve without an argument throws an error in LG WebOS 2
var promise = Promise$1.resolve(undefined);
var promise = Promise$1.resolve();
notify = function () {
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) {
_anObject(C);
if (_isObject(x) && x.constructor === C) return x;
@ -2728,12 +2714,9 @@ var microtask = _microtask();
var PROMISE = 'Promise';
var TypeError$1 = _global.TypeError;
var process$2 = _global.process;
var versions = process$2 && process$2.versions;
var v8 = versions && versions.v8 || '';
var $Promise = _global[PROMISE];
var isNode$1 = _classof(process$2) == 'process';
var empty = function () { /* empty */ };
@ -2748,13 +2731,7 @@ var USE_NATIVE = !!function () {
exec(empty, empty);
};
// unhandled rejections tracking support, NodeJS Promise without it fails @@species test
return (isNode$1 || typeof PromiseRejectionEvent == 'function')
&& 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;
return (isNode$1 || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise;
} catch (e) { /* empty */ }
}();
@ -2776,7 +2753,7 @@ var notify = function (promise, isReject) {
var resolve = reaction.resolve;
var reject = reaction.reject;
var domain = reaction.domain;
var result, then, exited;
var result, then;
try {
if (handler) {
if (!ok) {
@ -2786,11 +2763,8 @@ var notify = function (promise, isReject) {
if (handler === true) result = value;
else {
if (domain) domain.enter();
result = handler(value); // may throw
if (domain) {
domain.exit();
exited = true;
}
result = handler(value);
if (domain) domain.exit();
}
if (result === reaction.promise) {
reject(TypeError$1('Promise-chain cycle'));
@ -2799,7 +2773,6 @@ var notify = function (promise, isReject) {
} else resolve(result);
} else reject(value);
} catch (e) {
if (domain && !exited) domain.exit();
reject(e);
}
};
@ -4182,6 +4155,10 @@ var _stringPad = function (that, maxLength, fillString, left) {
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
@ -5813,7 +5790,7 @@ var Storage = function () {
createClass(Storage, [{
key: 'get',
value: function get(key) {
if (!Storage.supported) {
if (!Storage.supported || !this.enabled) {
return null;
}
@ -5932,7 +5909,7 @@ var utils = {
return this.instanceof(input, Event);
},
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) {
return this.instanceof(input, TextTrack) || !this.nullOrUndefined(input) && this.string(input.kind);
@ -6043,26 +6020,25 @@ var utils = {
return;
}
var prefix = 'cache-';
var prefix = 'cache';
var hasId = utils.is.string(id);
var isCached = false;
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
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
// 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
if (!hasId || !exists()) {
@ -6078,13 +6054,12 @@ var utils = {
// Check in cache
if (useStorage) {
var cached = window.localStorage.getItem(prefix + id);
var cached = window.localStorage.getItem(prefix + '-' + id);
isCached = cached !== null;
if (isCached) {
var data = JSON.parse(cached);
injectSprite.call(container, data.content);
return;
update(container, data.content);
}
}
@ -6095,12 +6070,12 @@ var utils = {
}
if (useStorage) {
window.localStorage.setItem(prefix + id, JSON.stringify({
window.localStorage.setItem(prefix + '-' + id, JSON.stringify({
content: result
}));
}
injectSprite.call(container, result);
update(container, result);
}).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
closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) {
@ -7100,6 +7083,13 @@ var html5 = {
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
player.media.load();
@ -7108,9 +7098,6 @@ var html5 = {
player.play();
}
// Restore time
player.currentTime = currentTime;
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input
@ -7152,11 +7139,15 @@ var i18n = {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
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 '';
}
var string = config.i18n[key];
var string = utils.getDeep(config.i18n, key);
if (utils.is.empty(string)) {
return '';
}
var replace = {
'{seektime}': config.seekTime,
@ -7428,30 +7419,10 @@ var controls = {
this.elements.buttons[type] = button;
}
/* // Toggle aria-pressed state on a toggle button
// 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);
}, */
// Toggle classname when pressed property is set
var className = this.config.classNames.controlPressed;
Object.defineProperty(button, 'pressed', {
enumerable: true,
configurable: true,
get: function get() {
return utils.hasClass(button, className);
},
@ -7468,13 +7439,6 @@ var controls = {
// Create an <input type='range'>
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
var input = utils.createElement('input', utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
type: 'range',
@ -7485,7 +7449,7 @@ var controls = {
autocomplete: 'off',
// A11y fixes for https://github.com/sampotts/plyr/issues/905
role: 'slider',
'aria-labelledby': attributes.id + '-label',
'aria-label': i18n.get(type, this.config),
'aria-valuemin': 0,
'aria-valuemax': 100,
'aria-valuenow': 0
@ -7496,10 +7460,7 @@ var controls = {
// Set the fill for webkit now
controls.updateRangeFill.call(this, input);
return {
label: label,
input: input
};
return input;
},
@ -7861,27 +7822,7 @@ var controls = {
// Get the badge HTML for HD, 4K etc
var getBadge = function getBadge(quality) {
var label = '';
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;
}
var label = i18n.get('qualityBadge.' + quality, _this3.config);
if (!label.length) {
return null;
@ -7904,7 +7845,6 @@ var controls = {
// Translate a value into a nice label
// TODO: Localisation
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
@ -7912,7 +7852,13 @@ var controls = {
case 'quality':
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);
@ -8072,12 +8018,7 @@ var controls = {
// Generate options
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());
});
// Store reference
this.options.captions = tracks.map(function (track) {
return track.language;
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);
});
controls.updateSetting.call(this, type, list);
@ -8346,11 +8287,9 @@ var controls = {
var progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider
var seek = controls.createRange.call(this, 'seek', {
progress.appendChild(controls.createRange.call(this, 'seek', {
id: 'plyr-seek-' + data.id
});
progress.appendChild(seek.label);
progress.appendChild(seek.input);
}));
// Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer'));
@ -8400,11 +8339,9 @@ var controls = {
};
// 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
}));
volume.appendChild(range.label);
volume.appendChild(range.input);
})));
this.elements.volume = volume;
@ -8680,28 +8617,6 @@ var captions = {
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
if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
// Clear menu and hide
@ -8719,17 +8634,6 @@ var captions = {
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
var browser = utils.getBrowser();
@ -8752,14 +8656,52 @@ var captions = {
});
}
// Set language
captions.setLanguage.call(this);
// Try to load the value from storage
var active = this.storage.get('captions');
// Enable UI
captions.show.call(this);
// Otherwise fall back to the default config
if (!utils.is.boolean(active)) {
active = this.config.captions.active;
}
// Set available languages in list
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
// Set toggled state
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);
}
},
@ -8920,25 +8862,6 @@ var captions = {
} else {
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)
loadSprite: true,
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)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -9083,7 +9006,10 @@ var defaults$1 = {
// Captions settings
captions: {
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
@ -9140,7 +9066,15 @@ var defaults$1 = {
reset: 'Reset',
disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad'
advertisement: 'Ad',
qualityBadge: {
2160: '4K',
1440: 'HD',
1080: 'HD',
720: 'HD',
576: 'SD',
480: 'SD'
}
},
// URLs
@ -9228,9 +9162,8 @@ var defaults$1 = {
display: {
currentTime: '.plyr__time--current',
duration: '.plyr__time--duration',
buffer: '.plyr__progress--buffer',
played: '.plyr__progress--played',
loop: '.plyr__progress--loop',
buffer: '.plyr__progress__buffer',
loop: '.plyr__progress__loop', // Used later
volume: '.plyr__volume--display'
},
progress: '.plyr__progress',
@ -9600,8 +9533,10 @@ var ui = {
// Remove native controls
ui.toggleNativeControls.call(this);
// Captions
captions.setup.call(this);
// Setup captions for HTML5
if (this.isHTML5) {
captions.setup.call(this);
}
// Reset volume
this.volume = null;
@ -9654,6 +9589,12 @@ var ui = {
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
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 any that accept key input http://webaim.org/techniques/keyboard/
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;
}
@ -10341,6 +10282,12 @@ var Listeners = function () {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
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?
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
var show = 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;
var active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
// Toggle state
this.elements.buttons.captions.pressed = this.captions.active;
this.elements.buttons.captions.pressed = active;
// 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
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
// 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');
}
}
/**
@ -13020,21 +12962,16 @@ var Plyr = function () {
}, {
key: 'currentTime',
set: function set(input) {
var targetTime = 0;
if (utils.is.number(input)) {
targetTime = input;
// Bail if media duration isn't available yet
if (!this.duration) {
return;
}
// Normalise targetTime
if (targetTime < 0) {
targetTime = 0;
} else if (targetTime > this.duration) {
targetTime = this.duration;
}
// Validate input
var inputIsValid = utils.is.number(input) && input > 0;
// Set
this.media.currentTime = targetTime;
this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
// Logging
this.debug.log('Seeking to ' + this.currentTime + ' seconds');
@ -13093,11 +13030,11 @@ var Plyr = function () {
// Faux duration set via config
var fauxDuration = parseFloat(this.config.duration);
// True duration
var realDuration = this.media ? Number(this.media.duration) : 0;
// Media duration can be NaN before the media has loaded
var duration = (this.media || {}).duration || 0;
// If custom duration is funky, use regular duration
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
// If config duration is funky, use regular duration
return fauxDuration || duration;
}
/**
@ -13269,7 +13206,7 @@ var Plyr = function () {
quality = Number(input);
}
if (!utils.is.number(quality) || quality === 0) {
if (!utils.is.number(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