This commit is contained in:
Sam Potts 2018-06-11 16:19:11 +10:00
parent 7c6d4666e9
commit 38f10d4cc6
9 changed files with 112 additions and 59 deletions

View File

@ -2,9 +2,9 @@
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option: This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
* `Array` of options (this builds the default controls based on your choices) - `Array` of options (this builds the default controls based on your choices)
* `String` containing the desired HTML - `String` containing the desired HTML
* `Function` that will be executed and should return one of the above - `Function` that will be executed and should return one of the above
## Using default controls ## Using default controls
@ -81,14 +81,14 @@ The classes and data attributes used in your template should match the `selector
You need to add several placeholders to your HTML template that are replaced when rendering: You need to add several placeholders to your HTML template that are replaced when rendering:
* `{id}` - the dynamically generated ID for the player (for form controls) - `{id}` - the dynamically generated ID for the player (for form controls)
* `{seektime}` - the seek time specified in options for fast forward and rewind - `{seektime}` - the seek time specified in options for fast forward and rewind
* `{title}` - the title of your media, if specified - `{title}` - the title of your media, if specified
### Limitations ### Limitations
* Currently the settings menus are not supported with custom controls HTML - Currently the settings menus are not supported with custom controls HTML
* AirPlay and PiP buttons can be added but you will have to manage feature detection - AirPlay and PiP buttons can be added but you will have to manage feature detection
### Example ### Example
@ -131,13 +131,13 @@ const controls = `
<div class="plyr__volume"> <div class="plyr__volume">
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume"> <input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume">
</div> </div>
<button type="button" class="plyr__control" aria-label="Enable captions" data-plyr="captions"> <button type="button" class="plyr__control" data-plyr="captions">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span> <span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span>
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span> <span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span>
</button> </button>
<button type="button" class="plyr__control" aria-label="Enter fullscreen" data-plyr="fullscreen"> <button type="button" class="plyr__control" data-plyr="fullscreen">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span> <span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>

55
dist/plyr.js vendored
View File

@ -1789,7 +1789,6 @@ var i18n = {
var browser = utils.getBrowser(); var browser = utils.getBrowser();
var controls = { var controls = {
// Get icon URL // Get icon URL
getIconUrl: function getIconUrl() { getIconUrl: function getIconUrl() {
var url = new URL(this.config.iconUrl, window.location); var url = new URL(this.config.iconUrl, window.location);
@ -2168,6 +2167,23 @@ var controls = {
}, },
// Format a time for display
formatTime: function formatTime() {
var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var inverted = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
// Bail if the value isn't a number
if (!utils.is.number(time)) {
return time;
}
// Always display hours if duration is over an hour
var forceHours = utils.getHours(this.duration) > 0;
return utils.formatTime(time, forceHours, inverted);
},
// Update the displayed time // Update the displayed time
updateTimeDisplay: function updateTimeDisplay() { updateTimeDisplay: function updateTimeDisplay() {
var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
@ -2179,11 +2195,8 @@ var controls = {
return; return;
} }
// Always display hours if duration is over an hour
var forceHours = utils.getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.innerText = utils.formatTime(time, forceHours, inverted); target.innerText = controls.formatTime(time, inverted);
}, },
@ -2286,8 +2299,20 @@ var controls = {
return; return;
} }
// Set aria value for https://github.com/sampotts/plyr/issues/905 // Set aria values for https://github.com/sampotts/plyr/issues/905
range.setAttribute('aria-valuenow', range.value); if (utils.matches(range, this.config.selectors.inputs.seek)) {
range.setAttribute('aria-valuenow', this.currentTime);
var currentTime = controls.formatTime(this.currentTime);
var duration = controls.formatTime(this.duration);
var format = i18n.get('seekLabel', this.config);
range.setAttribute('aria-valuetext', format.replace('{currentTime}', currentTime).replace('{duration}', duration));
} else if (utils.matches(range, this.config.selectors.inputs.volume)) {
var percent = range.value * 100;
range.setAttribute('aria-valuenow', percent);
range.setAttribute('aria-valuetext', percent + '%');
} else {
range.setAttribute('aria-valuenow', range.value);
}
// WebKit only // WebKit only
if (!browser.isWebkit) { if (!browser.isWebkit) {
@ -2373,11 +2398,16 @@ var controls = {
// Show the duration on metadataloaded or durationchange events // Show the duration on metadataloaded or durationchange events
durationUpdate: function durationUpdate() { durationUpdate: function durationUpdate() {
// Bail if no ui or durationchange event triggered after playing/seek when invertTime is false // Bail if no UI or durationchange event triggered after playing/seek when invertTime is false
if (!this.supported.ui || !this.config.invertTime && this.currentTime) { if (!this.supported.ui || !this.config.invertTime && this.currentTime) {
return; return;
} }
// Update ARIA values
if (utils.is.element(this.elements.inputs.seek)) {
this.elements.inputs.seek.setAttribute('aria-valuemax', this.duration);
}
// If there's a spot to display duration // If there's a spot to display duration
var hasDuration = utils.is.element(this.elements.display.duration); var hasDuration = utils.is.element(this.elements.display.duration);
@ -3218,7 +3248,6 @@ var controls = {
Array.from(labels).forEach(function (label) { Array.from(labels).forEach(function (label) {
utils.toggleClass(label, _this7.config.classNames.hidden, false); utils.toggleClass(label, _this7.config.classNames.hidden, false);
utils.toggleClass(label, _this7.config.classNames.tooltip, true); utils.toggleClass(label, _this7.config.classNames.tooltip, true);
label.setAttribute('role', 'tooltip');
}); });
} }
} }
@ -3659,6 +3688,7 @@ var defaults$1 = {
pause: 'Pause', pause: 'Pause',
fastForward: 'Forward {seektime}s', fastForward: 'Forward {seektime}s',
seek: 'Seek', seek: 'Seek',
seekLabel: '{currentTime} of {duration}',
played: 'Played', played: 'Played',
buffered: 'Buffered', buffered: 'Buffered',
currentTime: 'Current time', currentTime: 'Current time',
@ -3673,6 +3703,7 @@ var defaults$1 = {
frameTitle: 'Player for {title}', frameTitle: 'Player for {title}',
captions: 'Captions', captions: 'Captions',
settings: 'Settings', settings: 'Settings',
menuBack: 'Go back to previous menu',
speed: 'Speed', speed: 'Speed',
normal: 'Normal', normal: 'Normal',
quality: 'Quality', quality: 'Quality',
@ -4223,9 +4254,6 @@ var ui = {
// If there's a media title set, use that for the label // If there's a media title set, use that for the label
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) { if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
label += ', ' + this.config.title; label += ', ' + this.config.title;
// Set container label
this.elements.container.setAttribute('aria-label', this.config.title);
} }
// If there's a play button, set label // If there's a play button, set label
@ -7080,9 +7108,6 @@ var Plyr = function () {
utils.wrap(this.media, this.elements.container); utils.wrap(this.media, this.elements.container);
} }
// Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0);
// Add style hook // Add style hook
ui.addStyleHook.call(this); ui.addStyleHook.call(this);

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

80
src/js/controls.js vendored
View File

@ -12,8 +12,6 @@ import utils from './utils';
const browser = utils.getBrowser(); const browser = utils.getBrowser();
const controls = { const controls = {
// Get icon URL // Get icon URL
getIconUrl() { getIconUrl() {
const url = new URL(this.config.iconUrl, window.location); const url = new URL(this.config.iconUrl, window.location);
@ -359,10 +357,14 @@ const controls = {
createTime(type) { createTime(type) {
const attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]); const attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]);
const container = utils.createElement('div', utils.extend(attributes, { const container = utils.createElement(
class: `plyr__time ${attributes.class}`, 'div',
'aria-label': i18n.get(type, this.config), utils.extend(attributes, {
}), '00:00'); class: `plyr__time ${attributes.class}`,
'aria-label': i18n.get(type, this.config),
}),
'00:00',
);
// Reference for updates // Reference for updates
this.elements.display[type] = container; this.elements.display[type] = container;
@ -403,6 +405,19 @@ const controls = {
list.appendChild(item); list.appendChild(item);
}, },
// Format a time for display
formatTime(time = 0, inverted = false) {
// Bail if the value isn't a number
if (!utils.is.number(time)) {
return time;
}
// Always display hours if duration is over an hour
const forceHours = utils.getHours(this.duration) > 0;
return utils.formatTime(time, forceHours, inverted);
},
// Update the displayed time // Update the displayed time
updateTimeDisplay(target = null, time = 0, inverted = false) { updateTimeDisplay(target = null, time = 0, inverted = false) {
// Bail if there's no element to display or the value isn't a number // Bail if there's no element to display or the value isn't a number
@ -410,11 +425,8 @@ const controls = {
return; return;
} }
// Always display hours if duration is over an hour
const forceHours = utils.getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.innerText = utils.formatTime(time, forceHours, inverted); target.innerText = controls.formatTime(time, inverted);
}, },
// Update volume UI and storage // Update volume UI and storage
@ -509,8 +521,20 @@ const controls = {
return; return;
} }
// Set aria value for https://github.com/sampotts/plyr/issues/905 // Set aria values for https://github.com/sampotts/plyr/issues/905
range.setAttribute('aria-valuenow', range.value); if (utils.matches(range, this.config.selectors.inputs.seek)) {
range.setAttribute('aria-valuenow', this.currentTime);
const currentTime = controls.formatTime(this.currentTime);
const duration = controls.formatTime(this.duration);
const format = i18n.get('seekLabel', this.config);
range.setAttribute('aria-valuetext', format.replace('{currentTime}', currentTime).replace('{duration}', duration));
} else if (utils.matches(range, this.config.selectors.inputs.volume)) {
const percent = range.value * 100;
range.setAttribute('aria-valuenow', percent);
range.setAttribute('aria-valuetext', `${percent}%`);
} else {
range.setAttribute('aria-valuenow', range.value);
}
// WebKit only // WebKit only
if (!browser.isWebkit) { if (!browser.isWebkit) {
@ -599,11 +623,16 @@ const controls = {
// Show the duration on metadataloaded or durationchange events // Show the duration on metadataloaded or durationchange events
durationUpdate() { durationUpdate() {
// Bail if no ui or durationchange event triggered after playing/seek when invertTime is false // Bail if no UI or durationchange event triggered after playing/seek when invertTime is false
if (!this.supported.ui || (!this.config.invertTime && this.currentTime)) { if (!this.supported.ui || (!this.config.invertTime && this.currentTime)) {
return; return;
} }
// Update ARIA values
if (utils.is.element(this.elements.inputs.seek)) {
this.elements.inputs.seek.setAttribute('aria-valuemax', this.duration);
}
// If there's a spot to display duration // If there's a spot to display duration
const hasDuration = utils.is.element(this.elements.display.duration); const hasDuration = utils.is.element(this.elements.display.duration);
@ -1126,9 +1155,11 @@ const controls = {
const progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress)); const progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider // Seek range slider
progress.appendChild(controls.createRange.call(this, 'seek', { progress.appendChild(
id: `plyr-seek-${data.id}`, controls.createRange.call(this, 'seek', {
})); id: `plyr-seek-${data.id}`,
}),
);
// Buffer progress // Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer')); progress.appendChild(controls.createProgress.call(this, 'buffer'));
@ -1182,13 +1213,15 @@ const controls = {
}; };
// Create the volume range slider // Create the volume range slider
volume.appendChild(controls.createRange.call( volume.appendChild(
this, controls.createRange.call(
'volume', this,
utils.extend(attributes, { 'volume',
id: `plyr-volume-${data.id}`, utils.extend(attributes, {
}), id: `plyr-volume-${data.id}`,
)); }),
),
);
this.elements.volume = volume; this.elements.volume = volume;
@ -1463,7 +1496,6 @@ const controls = {
Array.from(labels).forEach(label => { Array.from(labels).forEach(label => {
utils.toggleClass(label, this.config.classNames.hidden, false); utils.toggleClass(label, this.config.classNames.hidden, false);
utils.toggleClass(label, this.config.classNames.tooltip, true); utils.toggleClass(label, this.config.classNames.tooltip, true);
label.setAttribute('role', 'tooltip');
}); });
} }
}, },

View File

@ -169,6 +169,7 @@ const defaults = {
pause: 'Pause', pause: 'Pause',
fastForward: 'Forward {seektime}s', fastForward: 'Forward {seektime}s',
seek: 'Seek', seek: 'Seek',
seekLabel: '{currentTime} of {duration}',
played: 'Played', played: 'Played',
buffered: 'Buffered', buffered: 'Buffered',
currentTime: 'Current time', currentTime: 'Current time',
@ -183,6 +184,7 @@ const defaults = {
frameTitle: 'Player for {title}', frameTitle: 'Player for {title}',
captions: 'Captions', captions: 'Captions',
settings: 'Settings', settings: 'Settings',
menuBack: 'Go back to previous menu',
speed: 'Speed', speed: 'Speed',
normal: 'Normal', normal: 'Normal',
quality: 'Quality', quality: 'Quality',

View File

@ -260,9 +260,6 @@ class Plyr {
utils.wrap(this.media, this.elements.container); utils.wrap(this.media, this.elements.container);
} }
// Allow focus to be captured
this.elements.container.setAttribute('tabindex', 0);
// Add style hook // Add style hook
ui.addStyleHook.call(this); ui.addStyleHook.call(this);
@ -849,7 +846,7 @@ class Plyr {
// Update state and trigger event // Update state and trigger event
if (active !== this.captions.active) { if (active !== this.captions.active) {
this.captions.active = active; this.captions.active = active;
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
} }
} }

View File

@ -127,9 +127,6 @@ const ui = {
// If there's a media title set, use that for the label // If there's a media title set, use that for the label
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) { if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
label += `, ${this.config.title}`; label += `, ${this.config.title}`;
// Set container label
this.elements.container.setAttribute('aria-label', this.config.title);
} }
// If there's a play button, set label // If there's a play button, set label