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 index-*.html
npm-debug.log npm-debug.log
*.webm *.webm
/package-lock.json package-lock.json
.idea/ .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> </svg>
<p>If you think Plyr's good, <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" <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> </p>
</aside> </aside>

View File

@ -2,7 +2,8 @@
// Typography // 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-base: 15;
$font-size-small: 13; $font-size-small: 13;

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

631
dist/plyr.js vendored
View File

@ -11,60 +11,92 @@ typeof navigator === "object" && (function (global, factory) {
var getConstructor = function getConstructor(input) { var getConstructor = function getConstructor(input) {
return input !== null && typeof input !== 'undefined' ? input.constructor : null; return input !== null && typeof input !== 'undefined' ? input.constructor : null;
}; };
var instanceOf = function instanceOf(input, constructor) { var instanceOf = function instanceOf(input, constructor) {
return Boolean(input && constructor && input instanceof 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 = { var is = {
object: function object(input) { nullOrUndefined: isNullOrUndefined,
return getConstructor(input) === Object; object: isObject,
}, number: isNumber,
number: function number(input) { string: isString,
return getConstructor(input) === Number && !Number.isNaN(input); boolean: isBoolean,
}, function: isFunction,
string: function string(input) { array: isArray,
return getConstructor(input) === String; weakMap: isWeakMap,
}, nodeList: isNodeList,
boolean: function boolean(input) { element: isElement,
return getConstructor(input) === Boolean; textNode: isTextNode,
}, event: isEvent,
function: function _function(input) { cue: isCue,
return getConstructor(input) === Function; track: isTrack,
}, url: isUrl,
array: function array(input) { empty: isEmpty
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;
}
}; };
// ========================================================================== // ==========================================================================
@ -1637,6 +1669,8 @@ typeof navigator === "object" && (function (global, factory) {
// Create a settings menu item // Create a settings menu item
createMenuItem: function createMenuItem(_ref) { createMenuItem: function createMenuItem(_ref) {
var _this = this;
var value = _ref.value, var value = _ref.value,
list = _ref.list, list = _ref.list,
type = _ref.type, type = _ref.type,
@ -1652,17 +1686,21 @@ typeof navigator === "object" && (function (global, factory) {
type: 'button', type: 'button',
role: 'menuitemradio', role: 'menuitemradio',
class: (this.config.classNames.control + ' ' + (attributes.class ? attributes.class : '')).trim(), 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 // We have to set as HTML incase of special characters
item.innerHTML = title; flex.innerHTML = title;
if (is.element(badge)) { if (is.element(badge)) {
item.appendChild(badge); flex.appendChild(badge);
} }
item.appendChild(flex);
Object.defineProperty(item, 'checked', { Object.defineProperty(item, 'checked', {
enumerable: true, enumerable: true,
get: function get$$1() { 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); list.appendChild(item);
}, },
@ -1755,7 +1816,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update <progress> elements // Update <progress> elements
updateProgress: function updateProgress(event) { updateProgress: function updateProgress(event) {
var _this = this; var _this2 = this;
if (!this.supported.ui || !is.event(event)) { if (!this.supported.ui || !is.event(event)) {
return; return;
@ -1765,7 +1826,7 @@ typeof navigator === "object" && (function (global, factory) {
var setProgress = function setProgress(target, input) { var setProgress = function setProgress(target, input) {
var value = is.number(input) ? input : 0; 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 // Update value and label
if (is.element(progress)) { if (is.element(progress)) {
@ -1845,7 +1906,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update hover tooltip for seeking // Update hover tooltip for seeking
updateSeekTooltip: function updateSeekTooltip(event) { updateSeekTooltip: function updateSeekTooltip(event) {
var _this2 = this; var _this3 = this;
// Bail if setting not true // 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) { 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 visible = this.config.classNames.tooltip + '--visible';
var toggle = function toggle(_toggle) { var toggle = function toggle(_toggle) {
toggleClass(_this2.elements.display.seekTooltip, visible, _toggle); toggleClass(_this3.elements.display.seekTooltip, visible, _toggle);
}; };
// Hide on touch // 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 // Update the selected setting
updateSetting: function updateSetting(setting, container, input) { updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panels[setting]; 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 // Set the looping options
/* setLoopMenu() { /* setLoopMenu() {
// Menu required // Menu required
@ -2137,15 +2196,21 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages // Set a list of available captions languages
setCaptionsMenu: function setCaptionsMenu() { 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 // TODO: Captions or language? Currently it's mixed
var type = 'captions'; var type = 'captions';
var list = this.elements.settings.panels.captions.querySelector('[role="menu"]'); var list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
var tracks = captions.getTracks.call(this); var tracks = captions.getTracks.call(this);
var toggle = Boolean(tracks.length);
// Toggle the pane and tab // Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu // Empty the menu
emptyElement(list); emptyElement(list);
@ -2154,7 +2219,7 @@ typeof navigator === "object" && (function (global, factory) {
controls.checkMenu.call(this); controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!tracks.length) { if (!toggle) {
return; return;
} }
@ -2162,9 +2227,9 @@ typeof navigator === "object" && (function (global, factory) {
var options = tracks.map(function (track, value) { var options = tracks.map(function (track, value) {
return { return {
value: value, value: value,
checked: _this4.captions.toggled && _this4.currentTrack === value, checked: _this5.captions.toggled && _this5.currentTrack === value,
title: captions.getLabel.call(_this4, track), title: captions.getLabel.call(_this5, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()), badge: track.language && controls.createBadge.call(_this5, track.language.toUpperCase()),
list: list, list: list,
type: 'language' type: 'language'
}; };
@ -2188,12 +2253,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages // Set a list of available captions languages
setSpeedMenu: function setSpeedMenu(options) { setSpeedMenu: function setSpeedMenu(options) {
var _this5 = this; var _this6 = this;
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required // Menu required
if (!is.element(this.elements.settings.panels.speed)) { if (!is.element(this.elements.settings.panels.speed)) {
@ -2201,6 +2261,7 @@ typeof navigator === "object" && (function (global, factory) {
} }
var type = 'speed'; var type = 'speed';
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options // Set the speed options
if (is.array(options)) { if (is.array(options)) {
@ -2211,13 +2272,16 @@ typeof navigator === "object" && (function (global, factory) {
// Set options if passed and filter based on config // Set options if passed and filter based on config
this.options.speed = this.options.speed.filter(function (speed) { 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 // Toggle the pane and tab
var toggle = !is.empty(this.options.speed) && this.options.speed.length > 1; var toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent // Check if we need to toggle the parent
controls.checkMenu.call(this); controls.checkMenu.call(this);
@ -2226,19 +2290,13 @@ typeof navigator === "object" && (function (global, factory) {
return; return;
} }
// Get the list to populate
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items // Create items
this.options.speed.forEach(function (speed) { this.options.speed.forEach(function (speed) {
controls.createMenuItem.call(_this5, { controls.createMenuItem.call(_this6, {
value: speed, value: speed,
list: list, list: list,
type: type, 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 // Show a panel in the menu
showMenuPanel: function showMenuPanel() { showMenuPanel: function showMenuPanel() {
var _this6 = this; var _this7 = this;
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -2370,7 +2428,7 @@ typeof navigator === "object" && (function (global, factory) {
container.style.height = ''; container.style.height = '';
// Only listen once // 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 // 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 // Set attributes on current tab
toggleHidden(current, true); toggleHidden(current, true);
// current.setAttribute('tabindex', -1);
// Set attributes on target // Set attributes on target
toggleHidden(target, false); toggleHidden(target, false);
@ -2399,7 +2456,7 @@ typeof navigator === "object" && (function (global, factory) {
// Build the default HTML // Build the default HTML
// TODO: Set order based on order in the config.controls array? // TODO: Set order based on order in the config.controls array?
create: function create(data) { create: function create(data) {
var _this7 = this; var _this8 = this;
// Do nothing if we want no controls // Do nothing if we want no controls
if (is.empty(this.config.controls)) { if (is.empty(this.config.controls)) {
@ -2534,17 +2591,18 @@ typeof navigator === "object" && (function (global, factory) {
// Build the menu items // Build the menu items
this.config.settings.forEach(function (type) { 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', type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--forward', class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--forward',
role: 'menuitem', 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', { var value = createElement('span', {
class: _this7.config.classNames.menu.value class: _this8.config.classNames.menu.value
}); });
// Speed contains HTML entities // Speed contains HTML entities
@ -2563,10 +2621,10 @@ typeof navigator === "object" && (function (global, factory) {
// Back button // Back button
var back = createElement('button', { var back = createElement('button', {
type: 'button', type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--back' class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this7.config)); }, i18n.get(type, _this8.config));
back.addEventListener('click', function () { back.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, 'home'); controls.showMenuPanel.call(_this8, 'home');
}); });
pane.appendChild(back); pane.appendChild(back);
@ -2578,11 +2636,11 @@ typeof navigator === "object" && (function (global, factory) {
inner.appendChild(pane); inner.appendChild(pane);
menuItem.addEventListener('click', function () { menuItem.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, type); controls.showMenuPanel.call(_this8, type);
}); });
_this7.elements.settings.buttons[type] = menuItem; _this8.elements.settings.buttons[type] = menuItem;
_this7.elements.settings.panels[type] = pane; _this8.elements.settings.panels[type] = pane;
}); });
home.appendChild(menu); home.appendChild(menu);
@ -2630,7 +2688,7 @@ typeof navigator === "object" && (function (global, factory) {
// Insert controls // Insert controls
inject: function inject() { inject: function inject() {
var _this8 = this; var _this9 = this;
// Sprite // Sprite
if (this.config.loadSprite) { if (this.config.loadSprite) {
@ -2742,8 +2800,8 @@ typeof navigator === "object" && (function (global, factory) {
var labels = getElements.call(this, selector); var labels = getElements.call(this, selector);
Array.from(labels).forEach(function (label) { Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false); toggleClass(label, _this9.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true); toggleClass(label, _this9.config.classNames.tooltip, true);
}); });
} }
} }
@ -4374,7 +4432,8 @@ typeof navigator === "object" && (function (global, factory) {
// Clear timer // Clear timer
clearTimeout(_this2.player.timers.controls); 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 () { _this2.player.timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this2.player, false); return ui.toggleControls.call(_this2.player, false);
}, delay); }, delay);
@ -4531,124 +4590,104 @@ typeof navigator === "object" && (function (global, factory) {
}); });
} }
// Run default and custom handlers
}, {
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(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
}
// Trigger custom and default handlers
}, {
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 = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is.function(customHandler);
on.call(this.player, element, type, function (event) {
return _this4.proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
}
// Listen for control events // Listen for control events
}, { }, {
key: 'controls', key: 'controls',
value: function controls$$1() { value: function controls$$1() {
var _this4 = this; var _this5 = this;
// IE doesn't support input event, so we fallback to change // IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input'; 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];
var hasCustomHandler = is.function(customHandler);
var returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(_this4.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is.function(defaultHandler)) {
defaultHandler.call(_this4.player, event);
}
};
// Trigger custom and default handlers
var bind = function bind(element, type, defaultHandler, customHandlerKey) {
var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var customHandler = _this4.player.config.listeners[customHandlerKey];
var hasCustomHandler = is.function(customHandler);
on.call(_this4.player, element, type, function (event) {
return proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
};
// Play/pause toggle // Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(function (button) { 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 // 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 // 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 // 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 // Mute toggle
bind(this.player.elements.buttons.mute, 'click', function () { this.bind(this.player.elements.buttons.mute, 'click', function () {
_this4.player.muted = !_this4.player.muted; _this5.player.muted = !_this5.player.muted;
}, 'mute'); }, 'mute');
// Captions toggle // Captions toggle
bind(this.player.elements.buttons.captions, 'click', function () { this.bind(this.player.elements.buttons.captions, 'click', function () {
return _this4.player.toggleCaptions(); return _this5.player.toggleCaptions();
}); });
// Fullscreen toggle // Fullscreen toggle
bind(this.player.elements.buttons.fullscreen, 'click', function () { this.bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this4.player.fullscreen.toggle(); _this5.player.fullscreen.toggle();
}, 'fullscreen'); }, 'fullscreen');
// Picture-in-Picture // Picture-in-Picture
bind(this.player.elements.buttons.pip, 'click', function () { this.bind(this.player.elements.buttons.pip, 'click', function () {
_this4.player.pip = 'toggle'; _this5.player.pip = 'toggle';
}, 'pip'); }, 'pip');
// Airplay // 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 // Settings menu
bind(this.player.elements.buttons.settings, 'click', function (event) { this.bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this4.player, event); controls.toggleMenu.call(_this5.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');
}
}); });
// Set range input alternative "value", which matches the tooltip time (#954) // Set range input alternative "value", which matches the tooltip time (#954)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) { this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this4.player.elements.progress.getBoundingClientRect(); var clientRect = _this5.player.elements.progress.getBoundingClientRect();
var percent = 100 / clientRect.width * (event.pageX - clientRect.left); var percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent); event.currentTarget.setAttribute('seek-value', percent);
}); });
// Pause while seeking // 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 seek = event.currentTarget;
var code = event.keyCode ? event.keyCode : event.which; 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 we're done seeking and it was playing, resume playback
if (play && done) { if (play && done) {
seek.removeAttribute('play-on-seeked'); seek.removeAttribute('play-on-seeked');
_this4.player.play(); _this5.player.play();
} else if (!done && _this4.player.playing) { } else if (!done && _this5.player.playing) {
seek.setAttribute('play-on-seeked', ''); seek.setAttribute('play-on-seeked', '');
_this4.player.pause(); _this5.player.pause();
} }
}); });
// Seek // Seek
bind(this.player.elements.inputs.seek, inputEvent, function (event) { this.bind(this.player.elements.inputs.seek, inputEvent, function (event) {
var seek = event.currentTarget; var seek = event.currentTarget;
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954) // 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'); seek.removeAttribute('seek-value');
_this4.player.currentTime = seekTo / seek.max * _this4.player.duration; _this5.player.currentTime = seekTo / seek.max * _this5.player.duration;
}, 'seek'); }, 'seek');
// Current time invert // Current time invert
// Only if one time element is used for both currentTime and duration // Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.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 // Do nothing if we're at the start
if (_this4.player.currentTime === 0) { if (_this5.player.currentTime === 0) {
return; 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 // Volume
bind(this.player.elements.inputs.volume, inputEvent, function (event) { this.bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this4.player.volume = event.target.value; _this5.player.volume = event.target.value;
}, 'volume'); }, 'volume');
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) { if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) { Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) {
bind(element, 'input', function (event) { _this5.bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this4.player, event.target); return controls.updateRangeFill.call(_this5.player, event.target);
}); });
}); });
} }
// Seek tooltip // Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) { this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this4.player, event); return controls.updateSeekTooltip.call(_this5.player, event);
}); });
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) { this.bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter'; _this5.player.elements.controls.hover = !_this5.player.touch && event.type === 'mouseenter';
}); });
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) // 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) { this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); _this5.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); });
// Focus in/out on controls // Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', function (event) { this.bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this4.player, var _player = _this5.player,
config = _player.config, config = _player.config,
elements = _player.elements, elements = _player.elements,
timers = _player.timers; timers = _player.timers;
@ -4745,7 +4784,7 @@ typeof navigator === "object" && (function (global, factory) {
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle // Toggle
ui.toggleControls.call(_this4.player, event.type === 'focusin'); ui.toggleControls.call(_this5.player, event.type === 'focusin');
// If focusin, hide again after delay // If focusin, hide again after delay
if (event.type === 'focusin') { if (event.type === 'focusin') {
@ -4755,19 +4794,19 @@ typeof navigator === "object" && (function (global, factory) {
}, 0); }, 0);
// Delay a little more for keyboard users // Delay a little more for keyboard users
var delay = _this4.touch ? 3000 : 4000; var delay = _this5.touch ? 3000 : 4000;
// Clear timer // Clear timer
clearTimeout(timers.controls); clearTimeout(timers.controls);
// Hide // Hide
timers.controls = setTimeout(function () { timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this4.player, false); return ui.toggleControls.call(_this5.player, false);
}, delay); }, delay);
} }
}); });
// Mouse wheel for volume // 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 // Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves // Other browsers on OS X will be inverted until support improves
var inverted = event.webkitDirectionInvertedFromDevice; var inverted = event.webkitDirectionInvertedFromDevice;
@ -4777,10 +4816,10 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll down (or up on natural) to decrease // Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) { if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) { if (inverted) {
_this4.player.decreaseVolume(step); _this5.player.decreaseVolume(step);
direction = -1; direction = -1;
} else { } else {
_this4.player.increaseVolume(step); _this5.player.increaseVolume(step);
direction = 1; direction = 1;
} }
} }
@ -4788,16 +4827,16 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll up (or down on natural) to increase // Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) { if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) { if (inverted) {
_this4.player.increaseVolume(step); _this5.player.increaseVolume(step);
direction = 1; direction = 1;
} else { } else {
_this4.player.decreaseVolume(step); _this5.player.decreaseVolume(step);
direction = -1; direction = -1;
} }
} }
// Don't break page scrolling at max and min // 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(); event.preventDefault();
} }
}, 'volume', false); }, '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 // Create a settings menu item
createMenuItem: function createMenuItem(_ref) { createMenuItem: function createMenuItem(_ref) {
var _this = this;
var value = _ref.value, var value = _ref.value,
list = _ref.list, list = _ref.list,
type = _ref.type, type = _ref.type,
@ -7038,17 +7040,21 @@ typeof navigator === "object" && (function (global, factory) {
type: 'button', type: 'button',
role: 'menuitemradio', role: 'menuitemradio',
class: (this.config.classNames.control + ' ' + (attributes.class ? attributes.class : '')).trim(), 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 // We have to set as HTML incase of special characters
item.innerHTML = title; flex.innerHTML = title;
if (is$1.element(badge)) { if (is$1.element(badge)) {
item.appendChild(badge); flex.appendChild(badge);
} }
item.appendChild(flex);
Object.defineProperty(item, 'checked', { Object.defineProperty(item, 'checked', {
enumerable: true, enumerable: true,
get: function get() { 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); list.appendChild(item);
}, },
@ -7141,7 +7170,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update <progress> elements // Update <progress> elements
updateProgress: function updateProgress(event) { updateProgress: function updateProgress(event) {
var _this = this; var _this2 = this;
if (!this.supported.ui || !is$1.event(event)) { if (!this.supported.ui || !is$1.event(event)) {
return; return;
@ -7151,7 +7180,7 @@ typeof navigator === "object" && (function (global, factory) {
var setProgress = function setProgress(target, input) { var setProgress = function setProgress(target, input) {
var value = is$1.number(input) ? input : 0; 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 // Update value and label
if (is$1.element(progress)) { if (is$1.element(progress)) {
@ -7231,7 +7260,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update hover tooltip for seeking // Update hover tooltip for seeking
updateSeekTooltip: function updateSeekTooltip(event) { updateSeekTooltip: function updateSeekTooltip(event) {
var _this2 = this; var _this3 = this;
// Bail if setting not true // 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) { 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 visible = this.config.classNames.tooltip + '--visible';
var toggle = function toggle(_toggle) { var toggle = function toggle(_toggle) {
toggleClass(_this2.elements.display.seekTooltip, visible, _toggle); toggleClass(_this3.elements.display.seekTooltip, visible, _toggle);
}; };
// Hide on touch // 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 // Update the selected setting
updateSetting: function updateSetting(setting, container, input) { updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panels[setting]; 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 // Set the looping options
/* setLoopMenu() { /* setLoopMenu() {
// Menu required // Menu required
@ -7523,15 +7550,21 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages // Set a list of available captions languages
setCaptionsMenu: function setCaptionsMenu() { 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 // TODO: Captions or language? Currently it's mixed
var type = 'captions'; var type = 'captions';
var list = this.elements.settings.panels.captions.querySelector('[role="menu"]'); var list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
var tracks = captions.getTracks.call(this); var tracks = captions.getTracks.call(this);
var toggle = Boolean(tracks.length);
// Toggle the pane and tab // Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu // Empty the menu
emptyElement(list); emptyElement(list);
@ -7540,7 +7573,7 @@ typeof navigator === "object" && (function (global, factory) {
controls.checkMenu.call(this); controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!tracks.length) { if (!toggle) {
return; return;
} }
@ -7548,9 +7581,9 @@ typeof navigator === "object" && (function (global, factory) {
var options = tracks.map(function (track, value) { var options = tracks.map(function (track, value) {
return { return {
value: value, value: value,
checked: _this4.captions.toggled && _this4.currentTrack === value, checked: _this5.captions.toggled && _this5.currentTrack === value,
title: captions.getLabel.call(_this4, track), title: captions.getLabel.call(_this5, track),
badge: track.language && controls.createBadge.call(_this4, track.language.toUpperCase()), badge: track.language && controls.createBadge.call(_this5, track.language.toUpperCase()),
list: list, list: list,
type: 'language' type: 'language'
}; };
@ -7574,12 +7607,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set a list of available captions languages // Set a list of available captions languages
setSpeedMenu: function setSpeedMenu(options) { setSpeedMenu: function setSpeedMenu(options) {
var _this5 = this; var _this6 = this;
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required // Menu required
if (!is$1.element(this.elements.settings.panels.speed)) { if (!is$1.element(this.elements.settings.panels.speed)) {
@ -7587,6 +7615,7 @@ typeof navigator === "object" && (function (global, factory) {
} }
var type = 'speed'; var type = 'speed';
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options // Set the speed options
if (is$1.array(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 // Set options if passed and filter based on config
this.options.speed = this.options.speed.filter(function (speed) { 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 // Toggle the pane and tab
var toggle = !is$1.empty(this.options.speed) && this.options.speed.length > 1; var toggle = !is$1.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent // Check if we need to toggle the parent
controls.checkMenu.call(this); controls.checkMenu.call(this);
@ -7612,19 +7644,13 @@ typeof navigator === "object" && (function (global, factory) {
return; return;
} }
// Get the list to populate
var list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items // Create items
this.options.speed.forEach(function (speed) { this.options.speed.forEach(function (speed) {
controls.createMenuItem.call(_this5, { controls.createMenuItem.call(_this6, {
value: speed, value: speed,
list: list, list: list,
type: type, 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 // Show a panel in the menu
showMenuPanel: function showMenuPanel() { showMenuPanel: function showMenuPanel() {
var _this6 = this; var _this7 = this;
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
@ -7756,7 +7782,7 @@ typeof navigator === "object" && (function (global, factory) {
container.style.height = ''; container.style.height = '';
// Only listen once // 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 // Listen for the transition finishing and restore auto height/width
@ -7785,7 +7811,7 @@ typeof navigator === "object" && (function (global, factory) {
// Build the default HTML // Build the default HTML
// TODO: Set order based on order in the config.controls array? // TODO: Set order based on order in the config.controls array?
create: function create(data) { create: function create(data) {
var _this7 = this; var _this8 = this;
// Do nothing if we want no controls // Do nothing if we want no controls
if (is$1.empty(this.config.controls)) { if (is$1.empty(this.config.controls)) {
@ -7920,17 +7946,18 @@ typeof navigator === "object" && (function (global, factory) {
// Build the menu items // Build the menu items
this.config.settings.forEach(function (type) { 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', type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--forward', class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--forward',
role: 'menuitem', 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', { var value = createElement('span', {
class: _this7.config.classNames.menu.value class: _this8.config.classNames.menu.value
}); });
// Speed contains HTML entities // Speed contains HTML entities
@ -7949,10 +7976,10 @@ typeof navigator === "object" && (function (global, factory) {
// Back button // Back button
var back = createElement('button', { var back = createElement('button', {
type: 'button', type: 'button',
class: _this7.config.classNames.control + ' ' + _this7.config.classNames.control + '--back' class: _this8.config.classNames.control + ' ' + _this8.config.classNames.control + '--back'
}, i18n.get(type, _this7.config)); }, i18n.get(type, _this8.config));
back.addEventListener('click', function () { back.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, 'home'); controls.showMenuPanel.call(_this8, 'home');
}); });
pane.appendChild(back); pane.appendChild(back);
@ -7964,11 +7991,11 @@ typeof navigator === "object" && (function (global, factory) {
inner.appendChild(pane); inner.appendChild(pane);
menuItem.addEventListener('click', function () { menuItem.addEventListener('click', function () {
controls.showMenuPanel.call(_this7, type); controls.showMenuPanel.call(_this8, type);
}); });
_this7.elements.settings.buttons[type] = menuItem; _this8.elements.settings.buttons[type] = menuItem;
_this7.elements.settings.panels[type] = pane; _this8.elements.settings.panels[type] = pane;
}); });
home.appendChild(menu); home.appendChild(menu);
@ -8016,7 +8043,7 @@ typeof navigator === "object" && (function (global, factory) {
// Insert controls // Insert controls
inject: function inject() { inject: function inject() {
var _this8 = this; var _this9 = this;
// Sprite // Sprite
if (this.config.loadSprite) { if (this.config.loadSprite) {
@ -8128,8 +8155,8 @@ typeof navigator === "object" && (function (global, factory) {
var labels = getElements.call(this, selector); var labels = getElements.call(this, selector);
Array.from(labels).forEach(function (label) { Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false); toggleClass(label, _this9.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true); toggleClass(label, _this9.config.classNames.tooltip, true);
}); });
} }
} }
@ -9760,7 +9787,8 @@ typeof navigator === "object" && (function (global, factory) {
// Clear timer // Clear timer
clearTimeout(_this2.player.timers.controls); 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 () { _this2.player.timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this2.player, false); return ui.toggleControls.call(_this2.player, false);
}, delay); }, delay);
@ -9917,124 +9945,104 @@ typeof navigator === "object" && (function (global, factory) {
}); });
} }
// Run default and custom handlers
}, {
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(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is$1.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
}
// Trigger custom and default handlers
}, {
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 = this.player.config.listeners[customHandlerKey];
var hasCustomHandler = is$1.function(customHandler);
on.call(this.player, element, type, function (event) {
return _this4.proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
}
// Listen for control events // Listen for control events
}, { }, {
key: 'controls', key: 'controls',
value: function controls$$1() { value: function controls$$1() {
var _this4 = this; var _this5 = this;
// IE doesn't support input event, so we fallback to change // IE doesn't support input event, so we fallback to change
var inputEvent = browser.isIE ? 'change' : 'input'; 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];
var hasCustomHandler = is$1.function(customHandler);
var returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(_this4.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is$1.function(defaultHandler)) {
defaultHandler.call(_this4.player, event);
}
};
// Trigger custom and default handlers
var bind = function bind(element, type, defaultHandler, customHandlerKey) {
var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var customHandler = _this4.player.config.listeners[customHandlerKey];
var hasCustomHandler = is$1.function(customHandler);
on.call(_this4.player, element, type, function (event) {
return proxy(event, defaultHandler, customHandlerKey);
}, passive && !hasCustomHandler);
};
// Play/pause toggle // Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(function (button) { 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 // 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 // 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 // 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 // Mute toggle
bind(this.player.elements.buttons.mute, 'click', function () { this.bind(this.player.elements.buttons.mute, 'click', function () {
_this4.player.muted = !_this4.player.muted; _this5.player.muted = !_this5.player.muted;
}, 'mute'); }, 'mute');
// Captions toggle // Captions toggle
bind(this.player.elements.buttons.captions, 'click', function () { this.bind(this.player.elements.buttons.captions, 'click', function () {
return _this4.player.toggleCaptions(); return _this5.player.toggleCaptions();
}); });
// Fullscreen toggle // Fullscreen toggle
bind(this.player.elements.buttons.fullscreen, 'click', function () { this.bind(this.player.elements.buttons.fullscreen, 'click', function () {
_this4.player.fullscreen.toggle(); _this5.player.fullscreen.toggle();
}, 'fullscreen'); }, 'fullscreen');
// Picture-in-Picture // Picture-in-Picture
bind(this.player.elements.buttons.pip, 'click', function () { this.bind(this.player.elements.buttons.pip, 'click', function () {
_this4.player.pip = 'toggle'; _this5.player.pip = 'toggle';
}, 'pip'); }, 'pip');
// Airplay // 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 // Settings menu
bind(this.player.elements.buttons.settings, 'click', function (event) { this.bind(this.player.elements.buttons.settings, 'click', function (event) {
controls.toggleMenu.call(_this4.player, event); controls.toggleMenu.call(_this5.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');
}
}); });
// Set range input alternative "value", which matches the tooltip time (#954) // Set range input alternative "value", which matches the tooltip time (#954)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) { this.bind(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) {
var clientRect = _this4.player.elements.progress.getBoundingClientRect(); var clientRect = _this5.player.elements.progress.getBoundingClientRect();
var percent = 100 / clientRect.width * (event.pageX - clientRect.left); var percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent); event.currentTarget.setAttribute('seek-value', percent);
}); });
// Pause while seeking // 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 seek = event.currentTarget;
var code = event.keyCode ? event.keyCode : event.which; 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 we're done seeking and it was playing, resume playback
if (play && done) { if (play && done) {
seek.removeAttribute('play-on-seeked'); seek.removeAttribute('play-on-seeked');
_this4.player.play(); _this5.player.play();
} else if (!done && _this4.player.playing) { } else if (!done && _this5.player.playing) {
seek.setAttribute('play-on-seeked', ''); seek.setAttribute('play-on-seeked', '');
_this4.player.pause(); _this5.player.pause();
} }
}); });
// Seek // Seek
bind(this.player.elements.inputs.seek, inputEvent, function (event) { this.bind(this.player.elements.inputs.seek, inputEvent, function (event) {
var seek = event.currentTarget; var seek = event.currentTarget;
// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954) // 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'); seek.removeAttribute('seek-value');
_this4.player.currentTime = seekTo / seek.max * _this4.player.duration; _this5.player.currentTime = seekTo / seek.max * _this5.player.duration;
}, 'seek'); }, 'seek');
// Current time invert // Current time invert
// Only if one time element is used for both currentTime and duration // 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)) { 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 // Do nothing if we're at the start
if (_this4.player.currentTime === 0) { if (_this5.player.currentTime === 0) {
return; 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 // Volume
bind(this.player.elements.inputs.volume, inputEvent, function (event) { this.bind(this.player.elements.inputs.volume, inputEvent, function (event) {
_this4.player.volume = event.target.value; _this5.player.volume = event.target.value;
}, 'volume'); }, 'volume');
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) { if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) { Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(function (element) {
bind(element, 'input', function (event) { _this5.bind(element, 'input', function (event) {
return controls.updateRangeFill.call(_this4.player, event.target); return controls.updateRangeFill.call(_this5.player, event.target);
}); });
}); });
} }
// Seek tooltip // Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) { this.bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', function (event) {
return controls.updateSeekTooltip.call(_this4.player, event); return controls.updateSeekTooltip.call(_this5.player, event);
}); });
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) { this.bind(this.player.elements.controls, 'mouseenter mouseleave', function (event) {
_this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter'; _this5.player.elements.controls.hover = !_this5.player.touch && event.type === 'mouseenter';
}); });
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) // 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) { this.bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
_this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); _this5.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); });
// Focus in/out on controls // Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', function (event) { this.bind(this.player.elements.controls, 'focusin focusout', function (event) {
var _player = _this4.player, var _player = _this5.player,
config = _player.config, config = _player.config,
elements = _player.elements, elements = _player.elements,
timers = _player.timers; timers = _player.timers;
@ -10131,7 +10139,7 @@ typeof navigator === "object" && (function (global, factory) {
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
// Toggle // Toggle
ui.toggleControls.call(_this4.player, event.type === 'focusin'); ui.toggleControls.call(_this5.player, event.type === 'focusin');
// If focusin, hide again after delay // If focusin, hide again after delay
if (event.type === 'focusin') { if (event.type === 'focusin') {
@ -10141,19 +10149,19 @@ typeof navigator === "object" && (function (global, factory) {
}, 0); }, 0);
// Delay a little more for keyboard users // Delay a little more for keyboard users
var delay = _this4.touch ? 3000 : 4000; var delay = _this5.touch ? 3000 : 4000;
// Clear timer // Clear timer
clearTimeout(timers.controls); clearTimeout(timers.controls);
// Hide // Hide
timers.controls = setTimeout(function () { timers.controls = setTimeout(function () {
return ui.toggleControls.call(_this4.player, false); return ui.toggleControls.call(_this5.player, false);
}, delay); }, delay);
} }
}); });
// Mouse wheel for volume // 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 // Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves // Other browsers on OS X will be inverted until support improves
var inverted = event.webkitDirectionInvertedFromDevice; var inverted = event.webkitDirectionInvertedFromDevice;
@ -10163,10 +10171,10 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll down (or up on natural) to decrease // Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) { if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) { if (inverted) {
_this4.player.decreaseVolume(step); _this5.player.decreaseVolume(step);
direction = -1; direction = -1;
} else { } else {
_this4.player.increaseVolume(step); _this5.player.increaseVolume(step);
direction = 1; direction = 1;
} }
} }
@ -10174,16 +10182,16 @@ typeof navigator === "object" && (function (global, factory) {
// Scroll up (or down on natural) to increase // Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) { if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) { if (inverted) {
_this4.player.increaseVolume(step); _this5.player.increaseVolume(step);
direction = 1; direction = 1;
} else { } else {
_this4.player.decreaseVolume(step); _this5.player.decreaseVolume(step);
direction = -1; direction = -1;
} }
} }
// Don't break page scrolling at max and min // 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(); event.preventDefault();
} }
}, 'volume', false); }, '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', type: 'button',
role: 'menuitemradio', role: 'menuitemradio',
class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(), class: `${this.config.classNames.control} ${attributes.class ? attributes.class : ''}`.trim(),
value,
'aria-checked': checked, 'aria-checked': checked,
}) value,
}),
); );
const flex = createElement('span');
// We have to set as HTML incase of special characters // We have to set as HTML incase of special characters
item.innerHTML = title; flex.innerHTML = title;
if (is.element(badge)) { if (is.element(badge)) {
item.appendChild(badge); flex.appendChild(badge);
} }
item.appendChild(flex);
Object.defineProperty(item, 'checked', { Object.defineProperty(item, 'checked', {
enumerable: true, enumerable: true,
get() { 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); list.appendChild(item);
}, },
@ -657,95 +689,6 @@ const controls = {
toggleHidden(this.elements.settings.buttons[setting], !toggle); 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 // Update the selected setting
updateSetting(setting, container, input) { updateSetting(setting, container, input) {
const pane = this.elements.settings.panels[setting]; 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 // Set the looping options
/* setLoopMenu() { /* setLoopMenu() {
// Menu required // Menu required
@ -846,13 +876,19 @@ const controls = {
// Set a list of available captions languages // Set a list of available captions languages
setCaptionsMenu() { setCaptionsMenu() {
// Menu required
if (!is.element(this.elements.settings.panels.captions)) {
return;
}
// TODO: Captions or language? Currently it's mixed // TODO: Captions or language? Currently it's mixed
const type = 'captions'; const type = 'captions';
const list = this.elements.settings.panels.captions.querySelector('[role="menu"]'); const list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
const tracks = captions.getTracks.call(this); const tracks = captions.getTracks.call(this);
const toggle = Boolean(tracks.length);
// Toggle the pane and tab // Toggle the pane and tab
controls.toggleMenuButton.call(this, type, tracks.length); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu // Empty the menu
emptyElement(list); emptyElement(list);
@ -861,7 +897,7 @@ const controls = {
controls.checkMenu.call(this); controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!tracks.length) { if (!toggle) {
return; return;
} }
@ -892,17 +928,13 @@ const controls = {
// Set a list of available captions languages // Set a list of available captions languages
setSpeedMenu(options) { setSpeedMenu(options) {
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required // Menu required
if (!is.element(this.elements.settings.panels.speed)) { if (!is.element(this.elements.settings.panels.speed)) {
return; return;
} }
const type = 'speed'; const type = 'speed';
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Set the speed options // Set the speed options
if (is.array(options)) { if (is.array(options)) {
@ -918,6 +950,9 @@ const controls = {
const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1; const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleMenuButton.call(this, type, toggle); controls.toggleMenuButton.call(this, type, toggle);
// Empty the menu
emptyElement(list);
// Check if we need to toggle the parent // Check if we need to toggle the parent
controls.checkMenu.call(this); controls.checkMenu.call(this);
@ -926,12 +961,6 @@ const controls = {
return; return;
} }
// Get the list to populate
const list = this.elements.settings.panels.speed.querySelector('[role="menu"]');
// Empty the menu
emptyElement(list);
// Create items // Create items
this.options.speed.forEach(speed => { this.options.speed.forEach(speed => {
controls.createMenuItem.call(this, { controls.createMenuItem.call(this, {
@ -1069,7 +1098,6 @@ const controls = {
// Set attributes on current tab // Set attributes on current tab
toggleHidden(current, true); toggleHidden(current, true);
// current.setAttribute('tabindex', -1);
// Set attributes on target // Set attributes on target
toggleHidden(target, false); toggleHidden(target, false);
@ -1238,6 +1266,7 @@ const controls = {
class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`, class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
role: 'menuitem', role: 'menuitem',
'aria-haspopup': true, 'aria-haspopup': true,
hidden: '',
}), }),
); );

View File

@ -243,7 +243,8 @@ class Listeners {
// Clear timer // Clear timer
clearTimeout(this.player.timers.controls); 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); this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
}, },
); );
@ -394,58 +395,58 @@ class Listeners {
}); });
} }
// Run default and custom handlers
proxy(event, defaultHandler, customHandlerKey) {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
let returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
}
// Trigger custom and default handlers
bind(element, type, defaultHandler, customHandlerKey, passive = true) {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
on.call(
this.player,
element,
type,
event => this.proxy(event, defaultHandler, customHandlerKey),
passive && !hasCustomHandler,
);
}
// Listen for control events // Listen for control events
controls() { controls() {
// IE doesn't support input event, so we fallback to change // IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input'; const inputEvent = browser.isIE ? 'change' : 'input';
// Run default and custom handlers
const proxy = (event, defaultHandler, customHandlerKey) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
let returned = true;
// Execute custom handler
if (hasCustomHandler) {
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (returned && is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
};
// Trigger custom and default handlers
const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
on.call(
this.player,
element,
type,
event => proxy(event, defaultHandler, customHandlerKey),
passive && !hasCustomHandler,
);
};
// Play/pause toggle // Play/pause toggle
Array.from(this.player.elements.buttons.play).forEach(button => { 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 // 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 // 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 // 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 // Mute toggle
bind( this.bind(
this.player.elements.buttons.mute, this.player.elements.buttons.mute,
'click', 'click',
() => { () => {
@ -455,10 +456,10 @@ class Listeners {
); );
// Captions toggle // 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 // Fullscreen toggle
bind( this.bind(
this.player.elements.buttons.fullscreen, this.player.elements.buttons.fullscreen,
'click', 'click',
() => { () => {
@ -468,7 +469,7 @@ class Listeners {
); );
// Picture-in-Picture // Picture-in-Picture
bind( this.bind(
this.player.elements.buttons.pip, this.player.elements.buttons.pip,
'click', 'click',
() => { () => {
@ -478,62 +479,22 @@ class Listeners {
); );
// Airplay // 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 // 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); 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) // 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 clientRect = this.player.elements.progress.getBoundingClientRect();
const percent = 100 / clientRect.width * (event.pageX - clientRect.left); const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
event.currentTarget.setAttribute('seek-value', percent); event.currentTarget.setAttribute('seek-value', percent);
}); });
// Pause while seeking // 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 seek = event.currentTarget;
const code = event.keyCode ? event.keyCode : event.which; const code = event.keyCode ? event.keyCode : event.which;
@ -559,7 +520,7 @@ class Listeners {
}); });
// Seek // Seek
bind( this.bind(
this.player.elements.inputs.seek, this.player.elements.inputs.seek,
inputEvent, inputEvent,
event => { event => {
@ -582,7 +543,7 @@ class Listeners {
// Current time invert // Current time invert
// Only if one time element is used for both currentTime and duration // Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.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 // Do nothing if we're at the start
if (this.player.currentTime === 0) { if (this.player.currentTime === 0) {
return; return;
@ -595,7 +556,7 @@ class Listeners {
} }
// Volume // Volume
bind( this.bind(
this.player.elements.inputs.volume, this.player.elements.inputs.volume,
inputEvent, inputEvent,
event => { event => {
@ -607,27 +568,27 @@ class Listeners {
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) { if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => { 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 // 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), controls.updateSeekTooltip.call(this.player, event),
); );
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) // 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'; this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
}); });
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) // 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); this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
}); });
// Focus in/out on controls // 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; const { config, elements, timers } = this.player;
// Skip transition to prevent focus from scrolling the parent element // Skip transition to prevent focus from scrolling the parent element
@ -654,7 +615,7 @@ class Listeners {
}); });
// Mouse wheel for volume // Mouse wheel for volume
bind( this.bind(
this.player.elements.inputs.volume, this.player.elements.inputs.volume,
'wheel', 'wheel',
event => { event => {

View File

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

View File

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