Work on menus

This commit is contained in:
Sam Potts 2018-06-21 09:01:16 +10:00
parent bb546fe43f
commit 1f1d74ba50
18 changed files with 797 additions and 755 deletions

2
.gitignore vendored
View File

@ -6,6 +6,6 @@ aws.json
index-*.html
npm-debug.log
*.webm
/package-lock.json
package-lock.json
.idea/

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

2
demo/dist/error.css vendored

File diff suppressed because one or more lines are too long

View File

@ -166,7 +166,7 @@
</svg>
<p>If you think Plyr's good,
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&amp;url=http%3A%2F%2Fplyr.io&amp;via=Sam_Potts"
target="_blank" data-shr-network="twitter">tweet it</a>
target="_blank" data-shr-network="twitter">tweet it</a> 👍
</p>
</aside>

View File

@ -2,7 +2,8 @@
// Typography
// ==========================================================================
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif;
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol';
$font-size-base: 15;
$font-size-small: 13;

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

609
dist/plyr.js vendored
View File

@ -11,60 +11,92 @@ typeof navigator === "object" && (function (global, factory) {
var getConstructor = function getConstructor(input) {
return input !== null && typeof input !== 'undefined' ? input.constructor : null;
};
var instanceOf = function instanceOf(input, constructor) {
return Boolean(input && constructor && input instanceof constructor);
};
var isNullOrUndefined = function isNullOrUndefined(input) {
return input === null || typeof input === 'undefined';
};
var isObject = function isObject(input) {
return getConstructor(input) === Object;
};
var isNumber = function isNumber(input) {
return getConstructor(input) === Number && !Number.isNaN(input);
};
var isString = function isString(input) {
return getConstructor(input) === String;
};
var isBoolean = function isBoolean(input) {
return getConstructor(input) === Boolean;
};
var isFunction = function isFunction(input) {
return getConstructor(input) === Function;
};
var isArray = function isArray(input) {
return Array.isArray(input);
};
var isWeakMap = function isWeakMap(input) {
return instanceOf(input, WeakMap);
};
var isNodeList = function isNodeList(input) {
return instanceOf(input, NodeList);
};
var isElement = function isElement(input) {
return instanceOf(input, Element);
};
var isTextNode = function isTextNode(input) {
return getConstructor(input) === Text;
};
var isEvent = function isEvent(input) {
return instanceOf(input, Event);
};
var isCue = function isCue(input) {
return instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
};
var isTrack = function isTrack(input) {
return instanceOf(input, TextTrack) || !isNullOrUndefined(input) && isString(input.kind);
};
var isEmpty = function isEmpty(input) {
return isNullOrUndefined(input) || (isString(input) || isArray(input) || isNodeList(input)) && !input.length || isObject(input) && !Object.keys(input).length;
};
var isUrl = function isUrl(input) {
// Accept a URL object
if (instanceOf(input, window.URL)) {
return true;
}
// Add the protocol if required
var string = input;
if (!input.startsWith('http://') || !input.startsWith('https://')) {
string = 'http://' + input;
}
try {
return !isEmpty(new URL(string).hostname);
} catch (e) {
return false;
}
};
var is = {
object: function object(input) {
return getConstructor(input) === Object;
},
number: function number(input) {
return getConstructor(input) === Number && !Number.isNaN(input);
},
string: function string(input) {
return getConstructor(input) === String;
},
boolean: function boolean(input) {
return getConstructor(input) === Boolean;
},
function: function _function(input) {
return getConstructor(input) === Function;
},
array: function array(input) {
return !is.nullOrUndefined(input) && Array.isArray(input);
},
weakMap: function weakMap(input) {
return instanceOf(input, WeakMap);
},
nodeList: function nodeList(input) {
return instanceOf(input, NodeList);
},
element: function element(input) {
return instanceOf(input, Element);
},
textNode: function textNode(input) {
return getConstructor(input) === Text;
},
event: function event(input) {
return instanceOf(input, Event);
},
cue: function cue(input) {
return instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
},
track: function track(input) {
return instanceOf(input, TextTrack) || !is.nullOrUndefined(input) && is.string(input.kind);
},
url: function url(input) {
return !is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
},
nullOrUndefined: function nullOrUndefined(input) {
return input === null || typeof input === 'undefined';
},
empty: function empty(input) {
return is.nullOrUndefined(input) || (is.string(input) || is.array(input) || is.nodeList(input)) && !input.length || is.object(input) && !Object.keys(input).length;
}
nullOrUndefined: isNullOrUndefined,
object: isObject,
number: isNumber,
string: isString,
boolean: isBoolean,
function: isFunction,
array: isArray,
weakMap: isWeakMap,
nodeList: isNodeList,
element: isElement,
textNode: isTextNode,
event: isEvent,
cue: isCue,
track: isTrack,
url: isUrl,
empty: isEmpty
};
// ==========================================================================
@ -1637,6 +1669,8 @@ typeof navigator === "object" && (function (global, factory) {
// Create a settings menu item
createMenuItem: function createMenuItem(_ref) {
var _this = this;
var value = _ref.value,
list = _ref.list,
type = _ref.type,
@ -1652,17 +1686,21 @@ typeof navigator === "object" && (function (global, factory) {
type: 'button',
role: 'menuitemradio',
class: (this.config.classNames.control + ' ' + (attributes.class ? attributes.class : '')).trim(),
value: value,
'aria-checked': checked
'aria-checked': checked,
value: value
}));
var flex = createElement('span');
// We have to set as HTML incase of special characters
item.innerHTML = title;
flex.innerHTML = title;
if (is.element(badge)) {
item.appendChild(badge);
flex.appendChild(badge);
}
item.appendChild(flex);
Object.defineProperty(item, 'checked', {
enumerable: true,
get: function get$$1() {
@ -1682,6 +1720,29 @@ typeof navigator === "object" && (function (global, factory) {
}
});
this.listeners.bind(item, 'click', function () {
item.checked = true;
switch (type) {
case 'language':
_this.currentTrack = Number(value);
break;
case 'quality':
_this.quality = value;
break;
case 'speed':
_this.speed = parseFloat(value);
break;
default:
break;
}
controls.showMenuPanel.call(_this, 'home');
}, type);
list.appendChild(item);
},
@ -1755,7 +1816,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update <progress> elements
updateProgress: function updateProgress(event) {
var _this = this;
var _this2 = this;
if (!this.supported.ui || !is.event(event)) {
return;
@ -1765,7 +1826,7 @@ typeof navigator === "object" && (function (global, factory) {
var setProgress = function setProgress(target, input) {
var value = is.number(input) ? input : 0;
var progress = is.element(target) ? target : _this.elements.display.buffer;
var progress = is.element(target) ? target : _this2.elements.display.buffer;
// Update value and label
if (is.element(progress)) {
@ -1845,7 +1906,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update hover tooltip for seeking
updateSeekTooltip: function updateSeekTooltip(event) {
var _this2 = this;
var _this3 = this;
// Bail if setting not true
if (!this.config.tooltips.seek || !is.element(this.elements.inputs.seek) || !is.element(this.elements.display.seekTooltip) || this.duration === 0) {
@ -1858,7 +1919,7 @@ typeof navigator === "object" && (function (global, factory) {
var visible = this.config.classNames.tooltip + '--visible';
var toggle = function toggle(_toggle) {
toggleClass(_this2.elements.display.seekTooltip, visible, _toggle);
toggleClass(_this3.elements.display.seekTooltip, visible, _toggle);
};
// Hide on touch
@ -1951,99 +2012,6 @@ typeof navigator === "object" && (function (global, factory) {
},
// Set the quality menu
setQualityMenu: function setQualityMenu(options) {
var _this3 = this;
// Menu required
if (!is.element(this.elements.settings.panels.quality)) {
console.warn('Not an element');
return;
}
var type = 'quality';
var list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is.array(options)) {
this.options.quality = dedupe(options).filter(function (quality) {
return _this3.config.quality.options.includes(quality);
});
}
// Toggle the pane and tab
console.warn(this.options.quality);
var toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Empty the menu
emptyElement(list);
// Get the badge HTML for HD, 4K etc
var getBadge = function getBadge(quality) {
var label = i18n.get('qualityBadge.' + quality, _this3.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(_this3, label);
};
// Sort options by the config and then render options
this.options.quality.sort(function (a, b) {
var sorting = _this3.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
controls.createMenuItem.call(_this3, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this3, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
},
// Translate a value into a nice label
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality':
if (is.number(value)) {
var label = i18n.get('qualityLabel.' + value, this.config);
if (!label.length) {
return value + 'p';
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Update the selected setting
updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panels[setting];
@ -2096,6 +2064,97 @@ typeof navigator === "object" && (function (global, factory) {
},
// Translate a value into a nice label
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality':
if (is.number(value)) {
var label = i18n.get('qualityLabel.' + value, this.config);
if (!label.length) {
return value + 'p';
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Set the quality menu
setQualityMenu: function setQualityMenu(options) {
var _this4 = this;
// Menu required
if (!is.element(this.elements.settings.panels.quality)) {
return;
}
var type = 'quality';
var list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is.array(options)) {
this.options.quality = dedupe(options).filter(function (quality) {
return _this4.config.quality.options.includes(quality);
});
}
// Toggle the pane and tab
var toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Get the badge HTML for HD, 4K etc
var getBadge = function getBadge(quality) {
var label = i18n.get('qualityBadge.' + quality, _this4.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(_this4, label);
};
// Sort options by the config and then render options
this.options.quality.sort(function (a, b) {
var sorting = _this4.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
controls.createMenuItem.call(_this4, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this4, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
},
// Set the looping options
/* setLoopMenu() {
// Menu required
@ -2137,15 +2196,21 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages
setCaptionsMenu: function setCaptionsMenu() {
var _this4 = this;
var _this5 = this;
// Menu required
if (!is.element(this.elements.settings.panels.captions)) {
return;
}
// TODO: Captions or language? Currently it's mixed
var type = 'captions';
var list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
var tracks = captions.getTracks.call(this);
var toggle = Boolean(tracks.length);
// Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length);
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
@ -2154,7 +2219,7 @@ typeof navigator === "object" && (function (global, factory) {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!tracks.length) {
if (!toggle) {
return;
}
@ -2162,9 +2227,9 @@ typeof navigator === "object" && (function (global, factory) {
var options = tracks.map(function (track, value) {
return {
value: value,
checked: _this4.captions.toggled && _this4.currentTrack === value,
title: captions.getLabel.call(_this4, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()),
checked: _this5.captions.toggled && _this5.currentTrack === value,
title: captions.getLabel.call(_this5, track),
badge: track.language && controls.createBadge.call(_this5, track.language.toUpperCase()),
list: list,
type: 'language'
};
@ -2188,12 +2253,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages
setSpeedMenu: function setSpeedMenu(options) {
var _this5 = this;
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
var _this6 = this;
// Menu required
if (!is.element(this.elements.settings.panels.speed)) {
@ -2201,6 +2261,7 @@ typeof navigator === "object" && (function (global, factory) {
}
var type = 'speed';
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options
if (is.array(options)) {
@ -2211,13 +2272,16 @@ typeof navigator === "object" && (function (global, factory) {
// Set options if passed and filter based on config
this.options.speed = this.options.speed.filter(function (speed) {
return _this5.config.speed.options.includes(speed);
return _this6.config.speed.options.includes(speed);
});
// Toggle the pane and tab
var toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
@ -2226,19 +2290,13 @@ typeof navigator === "object" && (function (global, factory) {
return;
}
// Get the list to populate
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items
this.options.speed.forEach(function (speed) {
controls.createMenuItem.call(_this5, {
controls.createMenuItem.call(_this6, {
value: speed,
list: list,
type: type,
title: controls.getLabel.call(_this5, 'speed', speed)
title: controls.getLabel.call(_this6, 'speed', speed)
});
});
@ -2332,7 +2390,7 @@ typeof navigator === "object" && (function (global, factory) {
// Show a panel in the menu
showMenuPanel: function showMenuPanel() {
var _this6 = this;
var _this7 = this;
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -2370,7 +2428,7 @@ typeof navigator === "object" && (function (global, factory) {
container.style.height = '';
// Only listen once
off.call(_this6, container, transitionEndEvent, restore);
off.call(_this7, container, transitionEndEvent, restore);
};
// Listen for the transition finishing and restore auto height/width
@ -2383,7 +2441,6 @@ typeof navigator === "object" && (function (global, factory) {
// Set attributes on current tab
toggleHidden(current, true);
// current.setAttribute('tabindex', -1);
// Set attributes on target
toggleHidden(target, false);
@ -2399,7 +2456,7 @@ typeof navigator === "object" && (function (global, factory) {
// Build the default HTML
// TODO: Set order based on order in the config.controls array?
create: function create(data) {
var _this7 = this;
var _this8 = this;
// Do nothing if we want no controls
if (is.empty(this.config.controls)) {
@ -2534,17 +2591,18 @@ typeof navigator === "object" && (function (global, factory) {
// Build the menu items
this.config.settings.forEach(function (type) {
var menuItem = createElement('button', extend(getAttributesFromSelector(_this7.config.selectors.buttons.settings), {
var menuItem = createElement('button', extend(getAttributesFromSelector(_this8.config.selectors.buttons.settings), {
type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--forward',
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--forward',
role: 'menuitem',
'aria-haspopup': true
'aria-haspopup': true,
hidden: ''
}));
var flex = createElement('span', null, i18n.get(type, _this7.config));
var flex = createElement('span', null, i18n.get(type, _this8.config));
var value = createElement('span', {
class: _this7.config.classNames.menu.value
class: _this8.config.classNames.menu.value
});
// Speed contains HTML entities
@ -2563,10 +2621,10 @@ typeof navigator === "object" && (function (global, factory) {
// Back button
var back = createElement('button', {
type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--back'
}, i18n.get(type, _this7.config));
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this8.config));
back.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, 'home');
controls.showMenuPanel.call(_this8, 'home');
});
pane.appendChild(back);
@ -2578,11 +2636,11 @@ typeof navigator === "object" && (function (global, factory) {
inner.appendChild(pane);
menuItem.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, type);
controls.showMenuPanel.call(_this8, type);
});
_this7.elements.settings.buttons[type] = menuItem;
_this7.elements.settings.panels[type] = pane;
_this8.elements.settings.buttons[type] = menuItem;
_this8.elements.settings.panels[type] = pane;
});
home.appendChild(menu);
@ -2630,7 +2688,7 @@ typeof navigator === "object" && (function (global, factory) {
// Insert controls
inject: function inject() {
var _this8 = this;
var _this9 = this;
// Sprite
if (this.config.loadSprite) {
@ -2742,8 +2800,8 @@ typeof navigator === "object" && (function (global, factory) {
var labels = getElements.call(this, selector);
Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true);
toggleClass(label, _this9.config.classNames.hidden, false);
toggleClass(label, _this9.config.classNames.tooltip, true);
});
}
}
@ -4374,7 +4432,8 @@ typeof navigator === "object" && (function (global, factory) {
// Clear timer
clearTimeout(_this2.player.timers.controls);
// Timer to prevent flicker when seeking
// Set new timer to prevent flicker when seeking
_this2.player.timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this2.player, false);
}, delay);
@ -4531,124 +4590,104 @@ typeof navigator === "object" && (function (global, factory) {
});
}
// Listen for control events
// Run default and custom handlers
}, {
key: 'controls',
value: function controls$$1() {
var _this4 = this;
// IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input';
// Run default and custom handlers
var proxy = function proxy(event, defaultHandler, customHandlerKey) {
var customHandler = _this4.player.config.listeners[customHandlerKey];
key: 'proxy',
value: function proxy(event, defaultHandler, customHandlerKey) {
var customHandler = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is.function(customHandler);
var returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(_this4.player, event);
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is.function(defaultHandler)) {
defaultHandler.call(_this4.player, event);
defaultHandler.call(this.player, event);
}
}
};
// Trigger custom and default handlers
var bind = function bind(element, type, defaultHandler, customHandlerKey) {
}, {
key: 'bind',
value: function bind(element, type, defaultHandler, customHandlerKey) {
var _this4 = this;
var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var customHandler = _this4.player.config.listeners[customHandlerKey];
var customHandler = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is.function(customHandler);
on.call(_this4.player, element, type, function (event) {
return proxy(event, defaultHandler, customHandlerKey);
on.call(this.player, element, type, function (event) {
return _this4.proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
};
}
// Listen for control events
}, {
key: 'controls',
value: function controls$$1() {
var _this5 = this;
// IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input';
// Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(function (button) {
bind(button, 'click', _this4.player.togglePlay, 'play');
_this5.bind(button, 'click', _this5.player.togglePlay, 'play');
});
// Pause
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
this.bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
this.bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
this.bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
bind(this.player.elements.buttons.mute, 'click', function () {
_this4.player.muted = !_this4.player.muted;
this.bind(this.player.elements.buttons.mute, 'click', function () {
_this5.player.muted = !_this5.player.muted;
}, 'mute');
// Captions toggle
bind(this.player.elements.buttons.captions, 'click', function () {
return _this4.player.toggleCaptions();
this.bind(this.player.elements.buttons.captions, 'click', function () {
return _this5.player.toggleCaptions();
});
// Fullscreen toggle
bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this4.player.fullscreen.toggle();
this.bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this5.player.fullscreen.toggle();
}, 'fullscreen');
// Picture-in-Picture
bind(this.player.elements.buttons.pip, 'click', function () {
_this4.player.pip = 'toggle';
this.bind(this.player.elements.buttons.pip, 'click', function () {
_this5.player.pip = 'toggle';
}, 'pip');
// Airplay
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this4.player, event);
});
// Settings menu
bind(this.player.elements.settings.popup, 'click', function (event) {
event.stopPropagation();
// Go back to home tab on click
var showHomeTab = function showHomeTab() {
controls.showMenuPanel.call(_this4.player, 'home');
};
// Settings menu items - use event delegation as items are added/removed
if (matches(event.target, _this4.player.config.selectors.inputs.language)) {
proxy(event, function () {
_this4.player.currentTrack = Number(event.target.value);
showHomeTab();
}, 'language');
} else if (matches(event.target, _this4.player.config.selectors.inputs.quality)) {
proxy(event, function () {
_this4.player.quality = event.target.value;
showHomeTab();
}, 'quality');
} else if (matches(event.target, _this4.player.config.selectors.inputs.speed)) {
proxy(event, function () {
_this4.player.speed = parseFloat(event.target.value);
showHomeTab();
}, 'speed');
}
this.bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this5.player, event);
});
// Set range input alternative "value", which matches the tooltip time (#954)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this4.player.elements.progress.getBoundingClientRect();
this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this5.player.elements.progress.getBoundingClientRect();
var percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// Pause while seeking
bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
this.bind(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;
@ -4666,15 +4705,15 @@ typeof navigator === "object" && (function (global, factory) {
// If we're done seeking and it was playing, resume playback
if (play && done) {
seek.removeAttribute('play-on-seeked');
_this4.player.play();
} else if (!done && _this4.player.playing) {
_this5.player.play();
} else if (!done && _this5.player.playing) {
seek.setAttribute('play-on-seeked', '');
_this4.player.pause();
_this5.player.pause();
}
});
// Seek
bind(this.player.elements.inputs.seek, inputEvent, function (event) {
this.bind(this.player.elements.inputs.seek, inputEvent, function (event) {
var seek = event.currentTarget;
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
@ -4686,56 +4725,56 @@ typeof navigator === "object" && (function (global, factory) {
seek.removeAttribute('seek-value');
_this4.player.currentTime = seekTo / seek.max * _this4.player.duration;
_this5.player.currentTime = seekTo / seek.max * _this5.player.duration;
}, 'seek');
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
bind(this.player.elements.display.currentTime, 'click', function () {
this.bind(this.player.elements.display.currentTime, 'click', function () {
// Do nothing if we're at the start
if (_this4.player.currentTime === 0) {
if (_this5.player.currentTime === 0) {
return;
}
_this4.player.config.invertTime = !_this4.player.config.invertTime;
_this5.player.config.invertTime = !_this5.player.config.invertTime;
controls.timeUpdate.call(_this4.player);
controls.timeUpdate.call(_this5.player);
});
}
// Volume
bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this4.player.volume = event.target.value;
this.bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this5.player.volume = event.target.value;
}, 'volume');
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) {
bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this4.player, event.target);
_this5.bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this5.player, event.target);
});
});
}
// Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this4.player, event);
this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this5.player, event);
});
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter';
this.bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this5.player.elements.controls.hover = !_this5.player.touch && event.type === 'mouseenter';
});
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this5.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this4.player,
this.bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this5.player,
config = _player.config,
elements = _player.elements,
timers = _player.timers;
@ -4745,7 +4784,7 @@ typeof navigator === "object" && (function (global, factory) {
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle
ui.toggleControls.call(_this4.player, event.type === 'focusin');
ui.toggleControls.call(_this5.player, event.type === 'focusin');
// If focusin, hide again after delay
if (event.type === 'focusin') {
@ -4755,19 +4794,19 @@ typeof navigator === "object" && (function (global, factory) {
}, 0);
// Delay a little more for keyboard users
var delay = _this4.touch ? 3000 : 4000;
var delay = _this5.touch ? 3000 : 4000;
// Clear timer
clearTimeout(timers.controls);
// Hide
timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this4.player, false);
return ui.toggleControls.call(_this5.player, false);
}, delay);
}
});
// Mouse wheel for volume
bind(this.player.elements.inputs.volume, 'wheel', function (event) {
this.bind(this.player.elements.inputs.volume, 'wheel', function (event) {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
var inverted = event.webkitDirectionInvertedFromDevice;
@ -4777,10 +4816,10 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
_this4.player.decreaseVolume(step);
_this5.player.decreaseVolume(step);
direction = -1;
} else {
_this4.player.increaseVolume(step);
_this5.player.increaseVolume(step);
direction = 1;
}
}
@ -4788,16 +4827,16 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
_this4.player.increaseVolume(step);
_this5.player.increaseVolume(step);
direction = 1;
} else {
_this4.player.decreaseVolume(step);
_this5.player.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if (direction === 1 && _this4.player.media.volume < 1 || direction === -1 && _this4.player.media.volume > 0) {
if (direction === 1 && _this5.player.media.volume < 1 || direction === -1 && _this5.player.media.volume > 0) {
event.preventDefault();
}
}, 'volume', false);

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

@ -7023,6 +7023,8 @@ typeof navigator === "object" && (function (global, factory) {
// Create a settings menu item
createMenuItem: function createMenuItem(_ref) {
var _this = this;
var value = _ref.value,
list = _ref.list,
type = _ref.type,
@ -7038,17 +7040,21 @@ typeof navigator === "object" && (function (global, factory) {
type: 'button',
role: 'menuitemradio',
class: (this.config.classNames.control + ' ' + (attributes.class ? attributes.class : '')).trim(),
value: value,
'aria-checked': checked
'aria-checked': checked,
value: value
}));
var flex = createElement('span');
// We have to set as HTML incase of special characters
item.innerHTML = title;
flex.innerHTML = title;
if (is$1.element(badge)) {
item.appendChild(badge);
flex.appendChild(badge);
}
item.appendChild(flex);
Object.defineProperty(item, 'checked', {
enumerable: true,
get: function get() {
@ -7068,6 +7074,29 @@ typeof navigator === "object" && (function (global, factory) {
}
});
this.listeners.bind(item, 'click', function () {
item.checked = true;
switch (type) {
case 'language':
_this.currentTrack = Number(value);
break;
case 'quality':
_this.quality = value;
break;
case 'speed':
_this.speed = parseFloat(value);
break;
default:
break;
}
controls.showMenuPanel.call(_this, 'home');
}, type);
list.appendChild(item);
},
@ -7141,7 +7170,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update <progress> elements
updateProgress: function updateProgress(event) {
var _this = this;
var _this2 = this;
if (!this.supported.ui || !is$1.event(event)) {
return;
@ -7151,7 +7180,7 @@ typeof navigator === "object" && (function (global, factory) {
var setProgress = function setProgress(target, input) {
var value = is$1.number(input) ? input : 0;
var progress = is$1.element(target) ? target : _this.elements.display.buffer;
var progress = is$1.element(target) ? target : _this2.elements.display.buffer;
// Update value and label
if (is$1.element(progress)) {
@ -7231,7 +7260,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update hover tooltip for seeking
updateSeekTooltip: function updateSeekTooltip(event) {
var _this2 = this;
var _this3 = this;
// Bail if setting not true
if (!this.config.tooltips.seek || !is$1.element(this.elements.inputs.seek) || !is$1.element(this.elements.display.seekTooltip) || this.duration === 0) {
@ -7244,7 +7273,7 @@ typeof navigator === "object" && (function (global, factory) {
var visible = this.config.classNames.tooltip + '--visible';
var toggle = function toggle(_toggle) {
toggleClass(_this2.elements.display.seekTooltip, visible, _toggle);
toggleClass(_this3.elements.display.seekTooltip, visible, _toggle);
};
// Hide on touch
@ -7337,99 +7366,6 @@ typeof navigator === "object" && (function (global, factory) {
},
// Set the quality menu
setQualityMenu: function setQualityMenu(options) {
var _this3 = this;
// Menu required
if (!is$1.element(this.elements.settings.panels.quality)) {
console.warn('Not an element');
return;
}
var type = 'quality';
var list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is$1.array(options)) {
this.options.quality = dedupe(options).filter(function (quality) {
return _this3.config.quality.options.includes(quality);
});
}
// Toggle the pane and tab
console.warn(this.options.quality);
var toggle = !is$1.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Empty the menu
emptyElement(list);
// Get the badge HTML for HD, 4K etc
var getBadge = function getBadge(quality) {
var label = i18n.get('qualityBadge.' + quality, _this3.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(_this3, label);
};
// Sort options by the config and then render options
this.options.quality.sort(function (a, b) {
var sorting = _this3.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
controls.createMenuItem.call(_this3, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this3, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
},
// Translate a value into a nice label
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality':
if (is$1.number(value)) {
var label = i18n.get('qualityLabel.' + value, this.config);
if (!label.length) {
return value + 'p';
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Update the selected setting
updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panels[setting];
@ -7482,6 +7418,97 @@ typeof navigator === "object" && (function (global, factory) {
},
// Translate a value into a nice label
getLabel: function getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality':
if (is$1.number(value)) {
var label = i18n.get('qualityLabel.' + value, this.config);
if (!label.length) {
return value + 'p';
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Set the quality menu
setQualityMenu: function setQualityMenu(options) {
var _this4 = this;
// Menu required
if (!is$1.element(this.elements.settings.panels.quality)) {
return;
}
var type = 'quality';
var list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is$1.array(options)) {
this.options.quality = dedupe(options).filter(function (quality) {
return _this4.config.quality.options.includes(quality);
});
}
// Toggle the pane and tab
var toggle = !is$1.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Get the badge HTML for HD, 4K etc
var getBadge = function getBadge(quality) {
var label = i18n.get('qualityBadge.' + quality, _this4.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(_this4, label);
};
// Sort options by the config and then render options
this.options.quality.sort(function (a, b) {
var sorting = _this4.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
controls.createMenuItem.call(_this4, {
value: quality,
list: list,
type: type,
title: controls.getLabel.call(_this4, 'quality', quality),
badge: getBadge(quality)
});
});
controls.updateSetting.call(this, type, list);
},
// Set the looping options
/* setLoopMenu() {
// Menu required
@ -7523,15 +7550,21 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages
setCaptionsMenu: function setCaptionsMenu() {
var _this4 = this;
var _this5 = this;
// Menu required
if (!is$1.element(this.elements.settings.panels.captions)) {
return;
}
// TODO: Captions or language? Currently it's mixed
var type = 'captions';
var list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
var tracks = captions.getTracks.call(this);
var toggle = Boolean(tracks.length);
// Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length);
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
@ -7540,7 +7573,7 @@ typeof navigator === "object" && (function (global, factory) {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!tracks.length) {
if (!toggle) {
return;
}
@ -7548,9 +7581,9 @@ typeof navigator === "object" && (function (global, factory) {
var options = tracks.map(function (track, value) {
return {
value: value,
checked: _this4.captions.toggled && _this4.currentTrack === value,
title: captions.getLabel.call(_this4, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()),
checked: _this5.captions.toggled && _this5.currentTrack === value,
title: captions.getLabel.call(_this5, track),
badge: track.language && controls.createBadge.call(_this5, track.language.toUpperCase()),
list: list,
type: 'language'
};
@ -7574,12 +7607,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages
setSpeedMenu: function setSpeedMenu(options) {
var _this5 = this;
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
var _this6 = this;
// Menu required
if (!is$1.element(this.elements.settings.panels.speed)) {
@ -7587,6 +7615,7 @@ typeof navigator === "object" && (function (global, factory) {
}
var type = 'speed';
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options
if (is$1.array(options)) {
@ -7597,13 +7626,16 @@ typeof navigator === "object" && (function (global, factory) {
// Set options if passed and filter based on config
this.options.speed = this.options.speed.filter(function (speed) {
return _this5.config.speed.options.includes(speed);
return _this6.config.speed.options.includes(speed);
});
// Toggle the pane and tab
var toggle = !is$1.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
@ -7612,19 +7644,13 @@ typeof navigator === "object" && (function (global, factory) {
return;
}
// Get the list to populate
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items
this.options.speed.forEach(function (speed) {
controls.createMenuItem.call(_this5, {
controls.createMenuItem.call(_this6, {
value: speed,
list: list,
type: type,
title: controls.getLabel.call(_this5, 'speed', speed)
title: controls.getLabel.call(_this6, 'speed', speed)
});
});
@ -7718,7 +7744,7 @@ typeof navigator === "object" && (function (global, factory) {
// Show a panel in the menu
showMenuPanel: function showMenuPanel() {
var _this6 = this;
var _this7 = this;
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -7756,7 +7782,7 @@ typeof navigator === "object" && (function (global, factory) {
container.style.height = '';
// Only listen once
off.call(_this6, container, transitionEndEvent, restore);
off.call(_this7, container, transitionEndEvent, restore);
};
// Listen for the transition finishing and restore auto height/width
@ -7785,7 +7811,7 @@ typeof navigator === "object" && (function (global, factory) {
// Build the default HTML
// TODO: Set order based on order in the config.controls array?
create: function create(data) {
var _this7 = this;
var _this8 = this;
// Do nothing if we want no controls
if (is$1.empty(this.config.controls)) {
@ -7920,17 +7946,18 @@ typeof navigator === "object" && (function (global, factory) {
// Build the menu items
this.config.settings.forEach(function (type) {
var menuItem = createElement('button', extend(getAttributesFromSelector(_this7.config.selectors.buttons.settings), {
var menuItem = createElement('button', extend(getAttributesFromSelector(_this8.config.selectors.buttons.settings), {
type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--forward',
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--forward',
role: 'menuitem',
'aria-haspopup': true
'aria-haspopup': true,
hidden: ''
}));
var flex = createElement('span', null, i18n.get(type, _this7.config));
var flex = createElement('span', null, i18n.get(type, _this8.config));
var value = createElement('span', {
class: _this7.config.classNames.menu.value
class: _this8.config.classNames.menu.value
});
// Speed contains HTML entities
@ -7949,10 +7976,10 @@ typeof navigator === "object" && (function (global, factory) {
// Back button
var back = createElement('button', {
type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--back'
}, i18n.get(type, _this7.config));
class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this8.config));
back.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, 'home');
controls.showMenuPanel.call(_this8, 'home');
});
pane.appendChild(back);
@ -7964,11 +7991,11 @@ typeof navigator === "object" && (function (global, factory) {
inner.appendChild(pane);
menuItem.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, type);
controls.showMenuPanel.call(_this8, type);
});
_this7.elements.settings.buttons[type] = menuItem;
_this7.elements.settings.panels[type] = pane;
_this8.elements.settings.buttons[type] = menuItem;
_this8.elements.settings.panels[type] = pane;
});
home.appendChild(menu);
@ -8016,7 +8043,7 @@ typeof navigator === "object" && (function (global, factory) {
// Insert controls
inject: function inject() {
var _this8 = this;
var _this9 = this;
// Sprite
if (this.config.loadSprite) {
@ -8128,8 +8155,8 @@ typeof navigator === "object" && (function (global, factory) {
var labels = getElements.call(this, selector);
Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true);
toggleClass(label, _this9.config.classNames.hidden, false);
toggleClass(label, _this9.config.classNames.tooltip, true);
});
}
}
@ -9760,7 +9787,8 @@ typeof navigator === "object" && (function (global, factory) {
// Clear timer
clearTimeout(_this2.player.timers.controls);
// Timer to prevent flicker when seeking
// Set new timer to prevent flicker when seeking
_this2.player.timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this2.player, false);
}, delay);
@ -9917,124 +9945,104 @@ typeof navigator === "object" && (function (global, factory) {
});
}
// Listen for control events
// Run default and custom handlers
}, {
key: 'controls',
value: function controls$$1() {
var _this4 = this;
// IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input';
// Run default and custom handlers
var proxy = function proxy(event, defaultHandler, customHandlerKey) {
var customHandler = _this4.player.config.listeners[customHandlerKey];
key: 'proxy',
value: function proxy(event, defaultHandler, customHandlerKey) {
var customHandler = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is$1.function(customHandler);
var returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(_this4.player, event);
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is$1.function(defaultHandler)) {
defaultHandler.call(_this4.player, event);
defaultHandler.call(this.player, event);
}
}
};
// Trigger custom and default handlers
var bind = function bind(element, type, defaultHandler, customHandlerKey) {
}, {
key: 'bind',
value: function bind(element, type, defaultHandler, customHandlerKey) {
var _this4 = this;
var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var customHandler = _this4.player.config.listeners[customHandlerKey];
var customHandler = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is$1.function(customHandler);
on.call(_this4.player, element, type, function (event) {
return proxy(event, defaultHandler, customHandlerKey);
on.call(this.player, element, type, function (event) {
return _this4.proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
};
}
// Listen for control events
}, {
key: 'controls',
value: function controls$$1() {
var _this5 = this;
// IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input';
// Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(function (button) {
bind(button, 'click', _this4.player.togglePlay, 'play');
_this5.bind(button, 'click', _this5.player.togglePlay, 'play');
});
// Pause
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
this.bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
this.bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
this.bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
bind(this.player.elements.buttons.mute, 'click', function () {
_this4.player.muted = !_this4.player.muted;
this.bind(this.player.elements.buttons.mute, 'click', function () {
_this5.player.muted = !_this5.player.muted;
}, 'mute');
// Captions toggle
bind(this.player.elements.buttons.captions, 'click', function () {
return _this4.player.toggleCaptions();
this.bind(this.player.elements.buttons.captions, 'click', function () {
return _this5.player.toggleCaptions();
});
// Fullscreen toggle
bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this4.player.fullscreen.toggle();
this.bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this5.player.fullscreen.toggle();
}, 'fullscreen');
// Picture-in-Picture
bind(this.player.elements.buttons.pip, 'click', function () {
_this4.player.pip = 'toggle';
this.bind(this.player.elements.buttons.pip, 'click', function () {
_this5.player.pip = 'toggle';
}, 'pip');
// Airplay
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this4.player, event);
});
// Settings menu
bind(this.player.elements.settings.popup, 'click', function (event) {
event.stopPropagation();
// Go back to home tab on click
var showHomeTab = function showHomeTab() {
controls.showMenuPanel.call(_this4.player, 'home');
};
// Settings menu items - use event delegation as items are added/removed
if (matches(event.target, _this4.player.config.selectors.inputs.language)) {
proxy(event, function () {
_this4.player.currentTrack = Number(event.target.value);
showHomeTab();
}, 'language');
} else if (matches(event.target, _this4.player.config.selectors.inputs.quality)) {
proxy(event, function () {
_this4.player.quality = event.target.value;
showHomeTab();
}, 'quality');
} else if (matches(event.target, _this4.player.config.selectors.inputs.speed)) {
proxy(event, function () {
_this4.player.speed = parseFloat(event.target.value);
showHomeTab();
}, 'speed');
}
this.bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this5.player, event);
});
// Set range input alternative "value", which matches the tooltip time (#954)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this4.player.elements.progress.getBoundingClientRect();
this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this5.player.elements.progress.getBoundingClientRect();
var percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// Pause while seeking
bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
this.bind(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;
@ -10052,15 +10060,15 @@ typeof navigator === "object" && (function (global, factory) {
// If we're done seeking and it was playing, resume playback
if (play && done) {
seek.removeAttribute('play-on-seeked');
_this4.player.play();
} else if (!done && _this4.player.playing) {
_this5.player.play();
} else if (!done && _this5.player.playing) {
seek.setAttribute('play-on-seeked', '');
_this4.player.pause();
_this5.player.pause();
}
});
// Seek
bind(this.player.elements.inputs.seek, inputEvent, function (event) {
this.bind(this.player.elements.inputs.seek, inputEvent, function (event) {
var seek = event.currentTarget;
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
@ -10072,56 +10080,56 @@ typeof navigator === "object" && (function (global, factory) {
seek.removeAttribute('seek-value');
_this4.player.currentTime = seekTo / seek.max * _this4.player.duration;
_this5.player.currentTime = seekTo / seek.max * _this5.player.duration;
}, 'seek');
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is$1.element(this.player.elements.display.duration)) {
bind(this.player.elements.display.currentTime, 'click', function () {
this.bind(this.player.elements.display.currentTime, 'click', function () {
// Do nothing if we're at the start
if (_this4.player.currentTime === 0) {
if (_this5.player.currentTime === 0) {
return;
}
_this4.player.config.invertTime = !_this4.player.config.invertTime;
_this5.player.config.invertTime = !_this5.player.config.invertTime;
controls.timeUpdate.call(_this4.player);
controls.timeUpdate.call(_this5.player);
});
}
// Volume
bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this4.player.volume = event.target.value;
this.bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this5.player.volume = event.target.value;
}, 'volume');
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) {
bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this4.player, event.target);
_this5.bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this5.player, event.target);
});
});
}
// Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this4.player, event);
this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this5.player, event);
});
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter';
this.bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this5.player.elements.controls.hover = !_this5.player.touch && event.type === 'mouseenter';
});
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this5.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this4.player,
this.bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this5.player,
config = _player.config,
elements = _player.elements,
timers = _player.timers;
@ -10131,7 +10139,7 @@ typeof navigator === "object" && (function (global, factory) {
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle
ui.toggleControls.call(_this4.player, event.type === 'focusin');
ui.toggleControls.call(_this5.player, event.type === 'focusin');
// If focusin, hide again after delay
if (event.type === 'focusin') {
@ -10141,19 +10149,19 @@ typeof navigator === "object" && (function (global, factory) {
}, 0);
// Delay a little more for keyboard users
var delay = _this4.touch ? 3000 : 4000;
var delay = _this5.touch ? 3000 : 4000;
// Clear timer
clearTimeout(timers.controls);
// Hide
timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this4.player, false);
return ui.toggleControls.call(_this5.player, false);
}, delay);
}
});
// Mouse wheel for volume
bind(this.player.elements.inputs.volume, 'wheel', function (event) {
this.bind(this.player.elements.inputs.volume, 'wheel', function (event) {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
var inverted = event.webkitDirectionInvertedFromDevice;
@ -10163,10 +10171,10 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
_this4.player.decreaseVolume(step);
_this5.player.decreaseVolume(step);
direction = -1;
} else {
_this4.player.increaseVolume(step);
_this5.player.increaseVolume(step);
direction = 1;
}
}
@ -10174,16 +10182,16 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
_this4.player.increaseVolume(step);
_this5.player.increaseVolume(step);
direction = 1;
} else {
_this4.player.decreaseVolume(step);
_this5.player.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if (direction === 1 && _this4.player.media.volume < 1 || direction === -1 && _this4.player.media.volume > 0) {
if (direction === 1 && _this5.player.media.volume < 1 || direction === -1 && _this5.player.media.volume > 0) {
event.preventDefault();
}
}, 'volume', false);

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

243
src/js/controls.js vendored
View File

@ -370,18 +370,22 @@ const controls = {
type: 'button',
role: 'menuitemradio',
class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(),
value,
'aria-checked': checked,
})
value,
}),
);
const flex = createElement('span');
// We have to set as HTML incase of special characters
item.innerHTML = title;
flex.innerHTML = title;
if (is.element(badge)) {
item.appendChild(badge);
flex.appendChild(badge);
}
item.appendChild(flex);
Object.defineProperty(item, 'checked', {
enumerable: true,
get() {
@ -399,6 +403,34 @@ const controls = {
},
});
this.listeners.bind(
item,
'click',
() => {
item.checked = true;
switch (type) {
case 'language':
this.currentTrack = Number(value);
break;
case 'quality':
this.quality = value;
break;
case 'speed':
this.speed = parseFloat(value);
break;
default:
break;
}
controls.showMenuPanel.call(this, 'home');
},
type,
);
list.appendChild(item);
},
@ -657,95 +689,6 @@ const controls = {
toggleHidden(this.elements.settings.buttons[setting], !toggle);
},
// Set the quality menu
setQualityMenu(options) {
// Menu required
if (!is.element(this.elements.settings.panels.quality)) {
console.warn('Not an element');
return;
}
const type = 'quality';
const list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is.array(options)) {
this.options.quality = dedupe(options).filter(quality => this.config.quality.options.includes(quality));
}
// Toggle the pane and tab
console.warn(this.options.quality);
const toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Empty the menu
emptyElement(list);
// Get the badge HTML for HD, 4K etc
const getBadge = quality => {
const label = i18n.get(`qualityBadge.${quality}`, this.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(this, label);
};
// Sort options by the config and then render options
this.options.quality
.sort((a, b) => {
const sorting = this.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
})
.forEach(quality => {
controls.createMenuItem.call(this, {
value: quality,
list,
type,
title: controls.getLabel.call(this, 'quality', quality),
badge: getBadge(quality),
});
});
controls.updateSetting.call(this, type, list);
},
// Translate a value into a nice label
getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : `${value}&times;`;
case 'quality':
if (is.number(value)) {
const label = i18n.get(`qualityLabel.${value}`, this.config);
if (!label.length) {
return `${value}p`;
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Update the selected setting
updateSetting(setting, container, input) {
const pane = this.elements.settings.panels[setting];
@ -797,6 +740,93 @@ const controls = {
}
},
// Translate a value into a nice label
getLabel(setting, value) {
switch (setting) {
case 'speed':
return value === 1 ? i18n.get('normal', this.config) : `${value}&times;`;
case 'quality':
if (is.number(value)) {
const label = i18n.get(`qualityLabel.${value}`, this.config);
if (!label.length) {
return `${value}p`;
}
return label;
}
return toTitleCase(value);
case 'captions':
return captions.getLabel.call(this);
default:
return null;
}
},
// Set the quality menu
setQualityMenu(options) {
// Menu required
if (!is.element(this.elements.settings.panels.quality)) {
return;
}
const type = 'quality';
const list = this.elements.settings.panels.quality.querySelector('[role="menu"]');
// Set options if passed and filter based on uniqueness and config
if (is.array(options)) {
this.options.quality = dedupe(options).filter(quality => this.config.quality.options.includes(quality));
}
// Toggle the pane and tab
const toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
}
// Get the badge HTML for HD, 4K etc
const getBadge = quality => {
const label = i18n.get(`qualityBadge.${quality}`, this.config);
if (!label.length) {
return null;
}
return controls.createBadge.call(this, label);
};
// Sort options by the config and then render options
this.options.quality
.sort((a, b) => {
const sorting = this.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
})
.forEach(quality => {
controls.createMenuItem.call(this, {
value: quality,
list,
type,
title: controls.getLabel.call(this, 'quality', quality),
badge: getBadge(quality),
});
});
controls.updateSetting.call(this, type, list);
},
// Set the looping options
/* setLoopMenu() {
// Menu required
@ -846,13 +876,19 @@ const controls = {
// Set a list of available captions languages
setCaptionsMenu() {
// Menu required
if (!is.element(this.elements.settings.panels.captions)) {
return;
}
// TODO: Captions or language? Currently it's mixed
const type = 'captions';
const list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
const tracks = captions.getTracks.call(this);
const toggle = Boolean(tracks.length);
// Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length);
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
@ -861,7 +897,7 @@ const controls = {
controls.checkMenu.call(this);
// If there's no captions, bail
if (!tracks.length) {
if (!toggle) {
return;
}
@ -892,17 +928,13 @@ const controls = {
// Set a list of available captions languages
setSpeedMenu(options) {
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required
if (!is.element(this.elements.settings.panels.speed)) {
return;
}
const type = 'speed';
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options
if (is.array(options)) {
@ -918,6 +950,9 @@ const controls = {
const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
@ -926,12 +961,6 @@ const controls = {
return;
}
// Get the list to populate
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items
this.options.speed.forEach(speed => {
controls.createMenuItem.call(this, {
@ -1069,7 +1098,6 @@ const controls = {
// Set attributes on current tab
toggleHidden(current, true);
// current.setAttribute('tabindex', -1);
// Set attributes on target
toggleHidden(target, false);
@ -1238,6 +1266,7 @@ const controls = {
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
role: 'menuitem',
'aria-haspopup': true,
hidden: '',
}),
);

View File

@ -243,7 +243,8 @@ class Listeners {
// Clear timer
clearTimeout(this.player.timers.controls);
// Timer to prevent flicker when seeking
// Set new timer to prevent flicker when seeking
this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
},
);
@ -394,13 +395,8 @@ class Listeners {
});
}
// Listen for control events
controls() {
// IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input';
// Run default and custom handlers
const proxy = (event, defaultHandler, customHandlerKey) => {
proxy(event, defaultHandler, customHandlerKey) {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
let returned = true;
@ -414,10 +410,10 @@ class Listeners {
if (returned && is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
};
}
// Trigger custom and default handlers
const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
bind(element, type, defaultHandler, customHandlerKey, passive = true) {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
@ -425,27 +421,32 @@ class Listeners {
this.player,
element,
type,
event => proxy(event, defaultHandler, customHandlerKey),
event => this.proxy(event, defaultHandler, customHandlerKey),
passive && !hasCustomHandler,
);
};
}
// Listen for control events
controls() {
// IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input';
// Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(button => {
bind(button, 'click', this.player.togglePlay, 'play');
this.bind(button, 'click', this.player.togglePlay, 'play');
});
// Pause
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
this.bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
this.bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
this.bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
bind(
this.bind(
this.player.elements.buttons.mute,
'click',
() => {
@ -455,10 +456,10 @@ class Listeners {
);
// Captions toggle
bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
this.bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
// Fullscreen toggle
bind(
this.bind(
this.player.elements.buttons.fullscreen,
'click',
() => {
@ -468,7 +469,7 @@ class Listeners {
);
// Picture-in-Picture
bind(
this.bind(
this.player.elements.buttons.pip,
'click',
() => {
@ -478,62 +479,22 @@ class Listeners {
);
// Airplay
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
this.bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
bind(this.player.elements.buttons.settings, 'click', event => {
this.bind(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
bind(this.player.elements.settings.popup, 'click', event => {
event.stopPropagation();
// Go back to home tab on click
const showHomeTab = () => {
controls.showMenuPanel.call(this.player, 'home');
};
// Settings menu items - use event delegation as items are added/removed
if (matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(
event,
() => {
this.player.currentTrack = Number(event.target.value);
showHomeTab();
},
'language',
);
} else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(
event,
() => {
this.player.quality = event.target.value;
showHomeTab();
},
'quality',
);
} else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(
event,
() => {
this.player.speed = parseFloat(event.target.value);
showHomeTab();
},
'speed',
);
}
});
// Set range input alternative "value", which matches the tooltip time (#954)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
const clientRect = this.player.elements.progress.getBoundingClientRect();
const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// Pause while seeking
bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
this.bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
const seek = event.currentTarget;
const code = event.keyCode ? event.keyCode : event.which;
@ -559,7 +520,7 @@ class Listeners {
});
// Seek
bind(
this.bind(
this.player.elements.inputs.seek,
inputEvent,
event => {
@ -582,7 +543,7 @@ class Listeners {
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
bind(this.player.elements.display.currentTime, 'click', () => {
this.bind(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.player.currentTime === 0) {
return;
@ -595,7 +556,7 @@ class Listeners {
}
// Volume
bind(
this.bind(
this.player.elements.inputs.volume,
inputEvent,
event => {
@ -607,27 +568,27 @@ class Listeners {
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
this.bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
});
}
// Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
controls.updateSeekTooltip.call(this.player, event),
);
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
});
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', event => {
this.bind(this.player.elements.controls, 'focusin focusout', event => {
const { config, elements, timers } = this.player;
// Skip transition to prevent focus from scrolling the parent element
@ -654,7 +615,7 @@ class Listeners {
});
// Mouse wheel for volume
bind(
this.bind(
this.player.elements.inputs.volume,
'wheel',
event => {

View File

@ -191,7 +191,7 @@
align-items: center;
display: flex;
margin-left: auto;
margin-right: -$plyr-control-padding;
margin-right: -($plyr-control-padding - 2);
overflow: hidden;
padding-left: ceil($plyr-control-padding * 3.5);
pointer-events: none;

View File

@ -22,3 +22,7 @@
width: 1px;
}
}
.plyr [hidden] {
display: none !important;
}