Merge pull request #1041 from sampotts/a11y-improvements

A11y improvements
This commit is contained in:
Sam Potts
2018-06-17 01:34:11 +10:00
committed by GitHub
21 changed files with 272 additions and 258 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
Again, more changes from @friday! Again, more changes from @friday!
- Restore window reference in `utils.is.cue()` - Restore window reference in `is.cue()`
- Fix InvalidStateError and IE11 issues - Fix InvalidStateError and IE11 issues
- Respect storage being disabled for storage getter - Respect storage being disabled for storage getter
+12 -12
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
@@ -105,7 +105,7 @@ const controls = `
<svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg> <svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg>
<span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span> <span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span>
</button> </button>
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Play, {title}" data-plyr="play"> <button type="button" class="plyr__control" aria-label="Play, {title}" data-plyr="play">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Pause</span> <span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
@@ -122,7 +122,7 @@ const controls = `
</div> </div>
<div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div> <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
<div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div> <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Mute" data-plyr="mute"> <button type="button" class="plyr__control" aria-label="Mute" data-plyr="mute">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg> <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg> <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span> <span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
@@ -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-pressed="true" 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-pressed="false" 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>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+81 -75
View File
@@ -595,30 +595,6 @@ typeof navigator === "object" && (function (global, factory) {
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false); toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
} }
// Toggle aria-pressed state on a toggle button
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
function toggleState(element, input) {
// If multiple elements passed
if (is.array(element) || is.nodeList(element)) {
Array.from(element).forEach(function (target) {
return toggleState(target, input);
});
return;
}
// Bail if no target
if (!is.element(element)) {
return;
}
// Get state
var pressed = element.getAttribute('aria-pressed') === 'true';
var state = is.boolean(input) ? input : !pressed;
// Set the attribute on target
element.setAttribute('aria-pressed', state);
}
// ========================================================================== // ==========================================================================
var transitionEndEvent = function () { var transitionEndEvent = function () {
@@ -1271,11 +1247,12 @@ typeof navigator === "object" && (function (global, factory) {
} }
// Render // Render
return '' + (inverted ? '-' : '') + hours + format(mins) + ':' + format(secs); return '' + (inverted && time > 0 ? '-' : '') + hours + format(mins) + ':' + format(secs);
} }
// ========================================================================== // ==========================================================================
// TODO: Don't export a massive object - break down and create class
var controls = { var controls = {
// Get icon URL // Get icon URL
getIconUrl: function getIconUrl() { getIconUrl: function getIconUrl() {
@@ -1289,8 +1266,7 @@ typeof navigator === "object" && (function (global, factory) {
}, },
// Find the UI controls and store references in custom controls // Find the UI controls
// TODO: Allow settings menus with custom controls
findElements: function findElements() { findElements: function findElements() {
try { try {
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
@@ -1386,12 +1362,11 @@ typeof navigator === "object" && (function (global, factory) {
pip: 'PIP', pip: 'PIP',
airplay: 'AirPlay' airplay: 'AirPlay'
}; };
var text = universals[type] || i18n.get(type, this.config); var text = universals[type] || i18n.get(type, this.config);
var attributes = Object.assign({}, attr, { var attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
}); });
return createElement('span', attributes, text); return createElement('span', attributes, text);
}, },
@@ -1493,9 +1468,6 @@ typeof navigator === "object" && (function (global, factory) {
// Label/Tooltip // Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' })); button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' })); button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
// Add aria attributes
attributes['aria-pressed'] = false;
} else { } else {
button.appendChild(controls.createIcon.call(this, icon)); button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label)); button.appendChild(controls.createLabel.call(this, label));
@@ -1517,19 +1489,26 @@ typeof navigator === "object" && (function (global, factory) {
this.elements.buttons[type] = button; this.elements.buttons[type] = button;
} }
// Toggle classname when pressed property is set
var className = this.config.classNames.controlPressed;
Object.defineProperty(button, 'pressed', {
enumerable: true,
get: function get$$1() {
return hasClass(button, className);
},
set: function set$$1() {
var pressed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
toggleClass(button, className, pressed);
}
});
return button; return button;
}, },
// Create an <input type='range'> // Create an <input type='range'>
createRange: function createRange(type, attributes) { createRange: function createRange(type, attributes) {
// Seek label
var label = createElement('label', {
for: attributes.id,
id: attributes.id + '-label',
class: this.config.classNames.hidden
}, i18n.get(type, this.config));
// Seek input // Seek input
var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), { var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
type: 'range', type: 'range',
@@ -1540,7 +1519,7 @@ typeof navigator === "object" && (function (global, factory) {
autocomplete: 'off', autocomplete: 'off',
// A11y fixes for https://github.com/sampotts/plyr/issues/905 // A11y fixes for https://github.com/sampotts/plyr/issues/905
role: 'slider', role: 'slider',
'aria-labelledby': attributes.id + '-label', 'aria-label': i18n.get(type, this.config),
'aria-valuemin': 0, 'aria-valuemin': 0,
'aria-valuemax': 100, 'aria-valuemax': 100,
'aria-valuenow': 0 'aria-valuenow': 0
@@ -1551,10 +1530,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set the fill for webkit now // Set the fill for webkit now
controls.updateRangeFill.call(this, input); controls.updateRangeFill.call(this, input);
return { return input;
label: label,
input: input
};
}, },
@@ -1576,7 +1552,6 @@ typeof navigator === "object" && (function (global, factory) {
played: 'played', played: 'played',
buffer: 'buffered' buffer: 'buffered'
}[type]; }[type];
var suffix = suffixKey ? i18n.get(suffixKey, this.config) : ''; var suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
progress.innerText = '% ' + suffix.toLowerCase(); progress.innerText = '% ' + suffix.toLowerCase();
@@ -1644,6 +1619,23 @@ typeof navigator === "object" && (function (global, factory) {
}, },
// Format a time for display
formatTime: function formatTime$$1() {
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 (!is.number(time)) {
return time;
}
// Always display hours if duration is over an hour
var forceHours = getHours(this.duration) > 0;
return 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;
@@ -1655,11 +1647,8 @@ typeof navigator === "object" && (function (global, factory) {
return; return;
} }
// Always display hours if duration is over an hour
var forceHours = getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.innerText = formatTime(time, forceHours, inverted); target.innerText = controls.formatTime(time, inverted);
}, },
@@ -1676,7 +1665,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update mute state // Update mute state
if (is.element(this.elements.buttons.mute)) { if (is.element(this.elements.buttons.mute)) {
toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); this.elements.buttons.mute.pressed = this.muted || this.volume === 0;
} }
}, },
@@ -1762,8 +1751,20 @@ typeof navigator === "object" && (function (global, factory) {
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 (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$$1 = i18n.get('seekLabel', this.config);
range.setAttribute('aria-valuetext', format$$1.replace('{currentTime}', currentTime).replace('{duration}', duration));
} else if (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) {
@@ -1849,11 +1850,16 @@ typeof navigator === "object" && (function (global, factory) {
// 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 (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 = is.element(this.elements.display.duration); var hasDuration = is.element(this.elements.display.duration);
@@ -2379,11 +2385,9 @@ typeof navigator === "object" && (function (global, factory) {
var progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); var progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider // Seek range slider
var seek = controls.createRange.call(this, 'seek', { progress.appendChild(controls.createRange.call(this, 'seek', {
id: 'plyr-seek-' + data.id id: 'plyr-seek-' + data.id
}); }));
progress.appendChild(seek.label);
progress.appendChild(seek.input);
// Buffer progress // Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer')); progress.appendChild(controls.createProgress.call(this, 'buffer'));
@@ -2433,11 +2437,9 @@ typeof navigator === "object" && (function (global, factory) {
}; };
// Create the volume range slider // Create the volume range slider
var range = controls.createRange.call(this, 'volume', extend(attributes, { volume.appendChild(controls.createRange.call(this, 'volume', extend(attributes, {
id: 'plyr-volume-' + data.id id: 'plyr-volume-' + data.id
})); })));
volume.appendChild(range.label);
volume.appendChild(range.input);
this.elements.volume = volume; this.elements.volume = volume;
@@ -2702,7 +2704,6 @@ typeof navigator === "object" && (function (global, factory) {
Array.from(labels).forEach(function (label) { Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false); toggleClass(label, _this8.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true); toggleClass(label, _this8.config.classNames.tooltip, true);
label.setAttribute('role', 'tooltip');
}); });
} }
} }
@@ -2977,7 +2978,7 @@ typeof navigator === "object" && (function (global, factory) {
} }
// Toggle state // Toggle state
toggleState(this.elements.buttons.captions, active); this.elements.buttons.captions.pressed = active;
// Add class hook // Add class hook
toggleClass(this.elements.container, activeClass, active); toggleClass(this.elements.container, activeClass, active);
@@ -3219,6 +3220,10 @@ typeof navigator === "object" && (function (global, factory) {
// Only allow one media playing at once (vimeo only) // Only allow one media playing at once (vimeo only)
autopause: true, autopause: true,
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
playsinline: true,
// Default time to skip when rewind/fast forward // Default time to skip when rewind/fast forward
seekTime: 10, seekTime: 10,
@@ -3332,6 +3337,7 @@ typeof navigator === "object" && (function (global, factory) {
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',
@@ -3346,6 +3352,7 @@ typeof navigator === "object" && (function (global, factory) {
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',
@@ -3475,6 +3482,7 @@ typeof navigator === "object" && (function (global, factory) {
posterEnabled: 'plyr__poster-enabled', posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads', ads: 'plyr__ads',
control: 'plyr__control', control: 'plyr__control',
controlPressed: 'plyr__control--pressed',
playing: 'plyr--playing', playing: 'plyr--playing',
paused: 'plyr--paused', paused: 'plyr--paused',
stopped: 'plyr--stopped', stopped: 'plyr--stopped',
@@ -3616,7 +3624,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update toggle button // Update toggle button
var button = this.player.elements.buttons.fullscreen; var button = this.player.elements.buttons.fullscreen;
if (is.element(button)) { if (is.element(button)) {
toggleState(button, this.active); button.pressed = this.active;
} }
// Trigger an event // Trigger an event
@@ -3986,9 +3994,6 @@ typeof navigator === "object" && (function (global, factory) {
// 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 (is.string(this.config.title) && !is.empty(this.config.title)) { if (is.string(this.config.title) && !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
@@ -4068,13 +4073,17 @@ typeof navigator === "object" && (function (global, factory) {
// Check playing state // Check playing state
checkPlaying: function checkPlaying(event) { checkPlaying: function checkPlaying(event) {
var _this3 = this;
// Class hooks // Class hooks
toggleClass(this.elements.container, this.config.classNames.playing, this.playing); toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
toggleClass(this.elements.container, this.config.classNames.paused, this.paused); toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
// Set ARIA state // Set state
toggleState(this.elements.buttons.play, this.playing); Array.from(this.elements.buttons.play).forEach(function (target) {
target.pressed = _this3.playing;
});
// Only update controls on non timeupdate events // Only update controls on non timeupdate events
if (is.event(event) && event.type === 'timeupdate') { if (is.event(event) && event.type === 'timeupdate') {
@@ -4088,7 +4097,7 @@ typeof navigator === "object" && (function (global, factory) {
// Check if media is loading // Check if media is loading
checkLoading: function checkLoading(event) { checkLoading: function checkLoading(event) {
var _this3 = this; var _this4 = this;
this.loading = ['stalled', 'waiting'].includes(event.type); this.loading = ['stalled', 'waiting'].includes(event.type);
@@ -4098,10 +4107,10 @@ typeof navigator === "object" && (function (global, factory) {
// Timer to prevent flicker when seeking // Timer to prevent flicker when seeking
this.timers.loading = setTimeout(function () { this.timers.loading = setTimeout(function () {
// Update progress bar loading class state // Update progress bar loading class state
toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); toggleClass(_this4.elements.container, _this4.config.classNames.loading, _this4.loading);
// Update controls visibility // Update controls visibility
ui.toggleControls.call(_this3); ui.toggleControls.call(_this4);
}, this.loading ? 250 : 0); }, this.loading ? 250 : 0);
}, },
@@ -7131,9 +7140,6 @@ typeof navigator === "object" && (function (global, factory) {
wrap(this.media, this.elements.container); 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);
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+81 -75
View File
@@ -5981,30 +5981,6 @@ typeof navigator === "object" && (function (global, factory) {
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false); toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
} }
// Toggle aria-pressed state on a toggle button
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
function toggleState(element, input) {
// If multiple elements passed
if (is$1.array(element) || is$1.nodeList(element)) {
Array.from(element).forEach(function (target) {
return toggleState(target, input);
});
return;
}
// Bail if no target
if (!is$1.element(element)) {
return;
}
// Get state
var pressed = element.getAttribute('aria-pressed') === 'true';
var state = is$1.boolean(input) ? input : !pressed;
// Set the attribute on target
element.setAttribute('aria-pressed', state);
}
// ========================================================================== // ==========================================================================
var transitionEndEvent = function () { var transitionEndEvent = function () {
@@ -6657,11 +6633,12 @@ typeof navigator === "object" && (function (global, factory) {
} }
// Render // Render
return '' + (inverted ? '-' : '') + hours + format(mins) + ':' + format(secs); return '' + (inverted && time > 0 ? '-' : '') + hours + format(mins) + ':' + format(secs);
} }
// ========================================================================== // ==========================================================================
// TODO: Don't export a massive object - break down and create class
var controls = { var controls = {
// Get icon URL // Get icon URL
getIconUrl: function getIconUrl() { getIconUrl: function getIconUrl() {
@@ -6675,8 +6652,7 @@ typeof navigator === "object" && (function (global, factory) {
}, },
// Find the UI controls and store references in custom controls // Find the UI controls
// TODO: Allow settings menus with custom controls
findElements: function findElements() { findElements: function findElements() {
try { try {
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
@@ -6772,12 +6748,11 @@ typeof navigator === "object" && (function (global, factory) {
pip: 'PIP', pip: 'PIP',
airplay: 'AirPlay' airplay: 'AirPlay'
}; };
var text = universals[type] || i18n.get(type, this.config); var text = universals[type] || i18n.get(type, this.config);
var attributes = Object.assign({}, attr, { var attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
}); });
return createElement('span', attributes, text); return createElement('span', attributes, text);
}, },
@@ -6879,9 +6854,6 @@ typeof navigator === "object" && (function (global, factory) {
// Label/Tooltip // Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' })); button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' })); button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
// Add aria attributes
attributes['aria-pressed'] = false;
} else { } else {
button.appendChild(controls.createIcon.call(this, icon)); button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label)); button.appendChild(controls.createLabel.call(this, label));
@@ -6903,19 +6875,26 @@ typeof navigator === "object" && (function (global, factory) {
this.elements.buttons[type] = button; this.elements.buttons[type] = button;
} }
// Toggle classname when pressed property is set
var className = this.config.classNames.controlPressed;
Object.defineProperty(button, 'pressed', {
enumerable: true,
get: function get() {
return hasClass(button, className);
},
set: function set() {
var pressed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
toggleClass(button, className, pressed);
}
});
return button; return button;
}, },
// Create an <input type='range'> // Create an <input type='range'>
createRange: function createRange(type, attributes) { createRange: function createRange(type, attributes) {
// Seek label
var label = createElement('label', {
for: attributes.id,
id: attributes.id + '-label',
class: this.config.classNames.hidden
}, i18n.get(type, this.config));
// Seek input // Seek input
var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), { var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
type: 'range', type: 'range',
@@ -6926,7 +6905,7 @@ typeof navigator === "object" && (function (global, factory) {
autocomplete: 'off', autocomplete: 'off',
// A11y fixes for https://github.com/sampotts/plyr/issues/905 // A11y fixes for https://github.com/sampotts/plyr/issues/905
role: 'slider', role: 'slider',
'aria-labelledby': attributes.id + '-label', 'aria-label': i18n.get(type, this.config),
'aria-valuemin': 0, 'aria-valuemin': 0,
'aria-valuemax': 100, 'aria-valuemax': 100,
'aria-valuenow': 0 'aria-valuenow': 0
@@ -6937,10 +6916,7 @@ typeof navigator === "object" && (function (global, factory) {
// Set the fill for webkit now // Set the fill for webkit now
controls.updateRangeFill.call(this, input); controls.updateRangeFill.call(this, input);
return { return input;
label: label,
input: input
};
}, },
@@ -6962,7 +6938,6 @@ typeof navigator === "object" && (function (global, factory) {
played: 'played', played: 'played',
buffer: 'buffered' buffer: 'buffered'
}[type]; }[type];
var suffix = suffixKey ? i18n.get(suffixKey, this.config) : ''; var suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
progress.innerText = '% ' + suffix.toLowerCase(); progress.innerText = '% ' + suffix.toLowerCase();
@@ -7030,6 +7005,23 @@ typeof navigator === "object" && (function (global, factory) {
}, },
// Format a time for display
formatTime: function formatTime$$1() {
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 (!is$1.number(time)) {
return time;
}
// Always display hours if duration is over an hour
var forceHours = getHours(this.duration) > 0;
return 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;
@@ -7041,11 +7033,8 @@ typeof navigator === "object" && (function (global, factory) {
return; return;
} }
// Always display hours if duration is over an hour
var forceHours = getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.innerText = formatTime(time, forceHours, inverted); target.innerText = controls.formatTime(time, inverted);
}, },
@@ -7062,7 +7051,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update mute state // Update mute state
if (is$1.element(this.elements.buttons.mute)) { if (is$1.element(this.elements.buttons.mute)) {
toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); this.elements.buttons.mute.pressed = this.muted || this.volume === 0;
} }
}, },
@@ -7148,8 +7137,20 @@ typeof navigator === "object" && (function (global, factory) {
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 (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$$1 = i18n.get('seekLabel', this.config);
range.setAttribute('aria-valuetext', format$$1.replace('{currentTime}', currentTime).replace('{duration}', duration));
} else if (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) {
@@ -7235,11 +7236,16 @@ typeof navigator === "object" && (function (global, factory) {
// 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 (is$1.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 = is$1.element(this.elements.display.duration); var hasDuration = is$1.element(this.elements.display.duration);
@@ -7765,11 +7771,9 @@ typeof navigator === "object" && (function (global, factory) {
var progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); var progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider // Seek range slider
var seek = controls.createRange.call(this, 'seek', { progress.appendChild(controls.createRange.call(this, 'seek', {
id: 'plyr-seek-' + data.id id: 'plyr-seek-' + data.id
}); }));
progress.appendChild(seek.label);
progress.appendChild(seek.input);
// Buffer progress // Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer')); progress.appendChild(controls.createProgress.call(this, 'buffer'));
@@ -7819,11 +7823,9 @@ typeof navigator === "object" && (function (global, factory) {
}; };
// Create the volume range slider // Create the volume range slider
var range = controls.createRange.call(this, 'volume', extend(attributes, { volume.appendChild(controls.createRange.call(this, 'volume', extend(attributes, {
id: 'plyr-volume-' + data.id id: 'plyr-volume-' + data.id
})); })));
volume.appendChild(range.label);
volume.appendChild(range.input);
this.elements.volume = volume; this.elements.volume = volume;
@@ -8088,7 +8090,6 @@ typeof navigator === "object" && (function (global, factory) {
Array.from(labels).forEach(function (label) { Array.from(labels).forEach(function (label) {
toggleClass(label, _this8.config.classNames.hidden, false); toggleClass(label, _this8.config.classNames.hidden, false);
toggleClass(label, _this8.config.classNames.tooltip, true); toggleClass(label, _this8.config.classNames.tooltip, true);
label.setAttribute('role', 'tooltip');
}); });
} }
} }
@@ -8363,7 +8364,7 @@ typeof navigator === "object" && (function (global, factory) {
} }
// Toggle state // Toggle state
toggleState(this.elements.buttons.captions, active); this.elements.buttons.captions.pressed = active;
// Add class hook // Add class hook
toggleClass(this.elements.container, activeClass, active); toggleClass(this.elements.container, activeClass, active);
@@ -8605,6 +8606,10 @@ typeof navigator === "object" && (function (global, factory) {
// Only allow one media playing at once (vimeo only) // Only allow one media playing at once (vimeo only)
autopause: true, autopause: true,
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
playsinline: true,
// Default time to skip when rewind/fast forward // Default time to skip when rewind/fast forward
seekTime: 10, seekTime: 10,
@@ -8718,6 +8723,7 @@ typeof navigator === "object" && (function (global, factory) {
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',
@@ -8732,6 +8738,7 @@ typeof navigator === "object" && (function (global, factory) {
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',
@@ -8861,6 +8868,7 @@ typeof navigator === "object" && (function (global, factory) {
posterEnabled: 'plyr__poster-enabled', posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads', ads: 'plyr__ads',
control: 'plyr__control', control: 'plyr__control',
controlPressed: 'plyr__control--pressed',
playing: 'plyr--playing', playing: 'plyr--playing',
paused: 'plyr--paused', paused: 'plyr--paused',
stopped: 'plyr--stopped', stopped: 'plyr--stopped',
@@ -9002,7 +9010,7 @@ typeof navigator === "object" && (function (global, factory) {
// Update toggle button // Update toggle button
var button = this.player.elements.buttons.fullscreen; var button = this.player.elements.buttons.fullscreen;
if (is$1.element(button)) { if (is$1.element(button)) {
toggleState(button, this.active); button.pressed = this.active;
} }
// Trigger an event // Trigger an event
@@ -9372,9 +9380,6 @@ typeof navigator === "object" && (function (global, factory) {
// 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 (is$1.string(this.config.title) && !is$1.empty(this.config.title)) { if (is$1.string(this.config.title) && !is$1.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
@@ -9454,13 +9459,17 @@ typeof navigator === "object" && (function (global, factory) {
// Check playing state // Check playing state
checkPlaying: function checkPlaying(event) { checkPlaying: function checkPlaying(event) {
var _this3 = this;
// Class hooks // Class hooks
toggleClass(this.elements.container, this.config.classNames.playing, this.playing); toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
toggleClass(this.elements.container, this.config.classNames.paused, this.paused); toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
// Set ARIA state // Set state
toggleState(this.elements.buttons.play, this.playing); Array.from(this.elements.buttons.play).forEach(function (target) {
target.pressed = _this3.playing;
});
// Only update controls on non timeupdate events // Only update controls on non timeupdate events
if (is$1.event(event) && event.type === 'timeupdate') { if (is$1.event(event) && event.type === 'timeupdate') {
@@ -9474,7 +9483,7 @@ typeof navigator === "object" && (function (global, factory) {
// Check if media is loading // Check if media is loading
checkLoading: function checkLoading(event) { checkLoading: function checkLoading(event) {
var _this3 = this; var _this4 = this;
this.loading = ['stalled', 'waiting'].includes(event.type); this.loading = ['stalled', 'waiting'].includes(event.type);
@@ -9484,10 +9493,10 @@ typeof navigator === "object" && (function (global, factory) {
// Timer to prevent flicker when seeking // Timer to prevent flicker when seeking
this.timers.loading = setTimeout(function () { this.timers.loading = setTimeout(function () {
// Update progress bar loading class state // Update progress bar loading class state
toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); toggleClass(_this4.elements.container, _this4.config.classNames.loading, _this4.loading);
// Update controls visibility // Update controls visibility
ui.toggleControls.call(_this3); ui.toggleControls.call(_this4);
}, this.loading ? 250 : 0); }, this.loading ? 250 : 0);
}, },
@@ -12511,9 +12520,6 @@ typeof navigator === "object" && (function (global, factory) {
wrap$2(this.media, this.elements.container); wrap$2(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);
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -15,7 +15,6 @@ import {
insertAfter, insertAfter,
removeElement, removeElement,
toggleClass, toggleClass,
toggleState,
} from './utils/elements'; } from './utils/elements';
import { on, triggerEvent } from './utils/events'; import { on, triggerEvent } from './utils/events';
import fetch from './utils/fetch'; import fetch from './utils/fetch';
@@ -193,7 +192,7 @@ const captions = {
} }
// Toggle state // Toggle state
toggleState(this.elements.buttons.captions, active); this.elements.buttons.captions.pressed = active;
// Add class hook // Add class hook
toggleClass(this.elements.container, activeClass, active); toggleClass(this.elements.container, activeClass, active);
+7
View File
@@ -18,6 +18,10 @@ const defaults = {
// Only allow one media playing at once (vimeo only) // Only allow one media playing at once (vimeo only)
autopause: true, autopause: true,
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
playsinline: true,
// Default time to skip when rewind/fast forward // Default time to skip when rewind/fast forward
seekTime: 10, seekTime: 10,
@@ -153,6 +157,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',
@@ -167,6 +172,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',
@@ -334,6 +340,7 @@ const defaults = {
posterEnabled: 'plyr__poster-enabled', posterEnabled: 'plyr__poster-enabled',
ads: 'plyr__ads', ads: 'plyr__ads',
control: 'plyr__control', control: 'plyr__control',
controlPressed: 'plyr__control--pressed',
playing: 'plyr--playing', playing: 'plyr--playing',
paused: 'plyr--paused', paused: 'plyr--paused',
stopped: 'plyr--stopped', stopped: 'plyr--stopped',
+69 -47
View File
@@ -20,7 +20,7 @@ import {
setAttributes, setAttributes,
toggleClass, toggleClass,
toggleHidden, toggleHidden,
toggleState, matches,
} from './utils/elements'; } from './utils/elements';
import { off, on } from './utils/events'; import { off, on } from './utils/events';
import is from './utils/is'; import is from './utils/is';
@@ -29,6 +29,7 @@ import { extend } from './utils/objects';
import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings'; import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
import { formatTime, getHours } from './utils/time'; import { formatTime, getHours } from './utils/time';
// TODO: Don't export a massive object - break down and create class
const controls = { const controls = {
// Get icon URL // Get icon URL
getIconUrl() { getIconUrl() {
@@ -41,8 +42,7 @@ const controls = {
}; };
}, },
// Find the UI controls and store references in custom controls // Find the UI controls
// TODO: Allow settings menus with custom controls
findElements() { findElements() {
try { try {
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
@@ -139,12 +139,11 @@ const controls = {
pip: 'PIP', pip: 'PIP',
airplay: 'AirPlay', airplay: 'AirPlay',
}; };
const text = universals[type] || i18n.get(type, this.config); const text = universals[type] || i18n.get(type, this.config);
const attributes = Object.assign({}, attr, { const attributes = Object.assign({}, attr, {
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '), class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '),
}); });
return createElement('span', attributes, text); return createElement('span', attributes, text);
}, },
@@ -250,9 +249,6 @@ const controls = {
// Label/Tooltip // Label/Tooltip
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' })); button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' })); button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
// Add aria attributes
attributes['aria-pressed'] = false;
} else { } else {
button.appendChild(controls.createIcon.call(this, icon)); button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label)); button.appendChild(controls.createLabel.call(this, label));
@@ -274,22 +270,23 @@ const controls = {
this.elements.buttons[type] = button; this.elements.buttons[type] = button;
} }
// Toggle classname when pressed property is set
const className = this.config.classNames.controlPressed;
Object.defineProperty(button, 'pressed', {
enumerable: true,
get() {
return hasClass(button, className);
},
set(pressed = false) {
toggleClass(button, className, pressed);
},
});
return button; return button;
}, },
// Create an <input type='range'> // Create an <input type='range'>
createRange(type, attributes) { createRange(type, attributes) {
// Seek label
const label = createElement(
'label',
{
for: attributes.id,
id: `${attributes.id}-label`,
class: this.config.classNames.hidden,
},
i18n.get(type, this.config),
);
// Seek input // Seek input
const input = createElement( const input = createElement(
'input', 'input',
@@ -304,7 +301,7 @@ const controls = {
autocomplete: 'off', autocomplete: 'off',
// A11y fixes for https://github.com/sampotts/plyr/issues/905 // A11y fixes for https://github.com/sampotts/plyr/issues/905
role: 'slider', role: 'slider',
'aria-labelledby': `${attributes.id}-label`, 'aria-label': i18n.get(type, this.config),
'aria-valuemin': 0, 'aria-valuemin': 0,
'aria-valuemax': 100, 'aria-valuemax': 100,
'aria-valuenow': 0, 'aria-valuenow': 0,
@@ -318,10 +315,7 @@ const controls = {
// Set the fill for webkit now // Set the fill for webkit now
controls.updateRangeFill.call(this, input); controls.updateRangeFill.call(this, input);
return { return input;
label,
input,
};
}, },
// Create a <progress> // Create a <progress>
@@ -349,7 +343,6 @@ const controls = {
played: 'played', played: 'played',
buffer: 'buffered', buffer: 'buffered',
}[type]; }[type];
const suffix = suffixKey ? i18n.get(suffixKey, this.config) : ''; const suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
progress.innerText = `% ${suffix.toLowerCase()}`; progress.innerText = `% ${suffix.toLowerCase()}`;
@@ -412,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 (!is.number(time)) {
return time;
}
// Always display hours if duration is over an hour
const forceHours = getHours(this.duration) > 0;
return 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
@@ -419,11 +425,8 @@ const controls = {
return; return;
} }
// Always display hours if duration is over an hour
const forceHours = getHours(this.duration) > 0;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target.innerText = formatTime(time, forceHours, inverted); target.innerText = controls.formatTime(time, inverted);
}, },
// Update volume UI and storage // Update volume UI and storage
@@ -439,7 +442,7 @@ const controls = {
// Update mute state // Update mute state
if (is.element(this.elements.buttons.mute)) { if (is.element(this.elements.buttons.mute)) {
toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); this.elements.buttons.mute.pressed = this.muted || this.volume === 0;
} }
}, },
@@ -518,8 +521,23 @@ 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 (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 (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) {
@@ -610,11 +628,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 (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 = is.element(this.elements.display.duration); const hasDuration = is.element(this.elements.display.duration);
@@ -1117,11 +1140,11 @@ const controls = {
const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider // Seek range slider
const seek = controls.createRange.call(this, 'seek', { progress.appendChild(
id: `plyr-seek-${data.id}`, controls.createRange.call(this, 'seek', {
}); id: `plyr-seek-${data.id}`,
progress.appendChild(seek.label); }),
progress.appendChild(seek.input); );
// Buffer progress // Buffer progress
progress.appendChild(controls.createProgress.call(this, 'buffer')); progress.appendChild(controls.createProgress.call(this, 'buffer'));
@@ -1175,15 +1198,15 @@ const controls = {
}; };
// Create the volume range slider // Create the volume range slider
const range = controls.createRange.call( volume.appendChild(
this, controls.createRange.call(
'volume', this,
extend(attributes, { 'volume',
id: `plyr-volume-${data.id}`, extend(attributes, {
}), id: `plyr-volume-${data.id}`,
}),
),
); );
volume.appendChild(range.label);
volume.appendChild(range.input);
this.elements.volume = volume; this.elements.volume = volume;
@@ -1448,7 +1471,6 @@ const controls = {
Array.from(labels).forEach(label => { Array.from(labels).forEach(label => {
toggleClass(label, this.config.classNames.hidden, false); toggleClass(label, this.config.classNames.hidden, false);
toggleClass(label, this.config.classNames.tooltip, true); toggleClass(label, this.config.classNames.tooltip, true);
label.setAttribute('role', 'tooltip');
}); });
} }
}, },
+2 -2
View File
@@ -4,7 +4,7 @@
// ========================================================================== // ==========================================================================
import browser from './utils/browser'; import browser from './utils/browser';
import { hasClass, toggleClass, toggleState, trapFocus } from './utils/elements'; import { hasClass, toggleClass, trapFocus } from './utils/elements';
import { on, triggerEvent } from './utils/events'; import { on, triggerEvent } from './utils/events';
import is from './utils/is'; import is from './utils/is';
@@ -16,7 +16,7 @@ function onChange() {
// Update toggle button // Update toggle button
const button = this.player.elements.buttons.fullscreen; const button = this.player.elements.buttons.fullscreen;
if (is.element(button)) { if (is.element(button)) {
toggleState(button, this.active); button.pressed = this.active;
} }
// Trigger an event // Trigger an event
-3
View File
@@ -263,9 +263,6 @@ class Plyr {
wrap(this.media, this.elements.container); 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);
+5 -6
View File
@@ -7,7 +7,7 @@ import controls from './controls';
import i18n from './i18n'; import i18n from './i18n';
import support from './support'; import support from './support';
import browser from './utils/browser'; import browser from './utils/browser';
import { getElement, toggleClass, toggleState } from './utils/elements'; import { getElement, toggleClass } from './utils/elements';
import { ready, triggerEvent } from './utils/events'; import { ready, triggerEvent } from './utils/events';
import is from './utils/is'; import is from './utils/is';
import loadImage from './utils/loadImage'; import loadImage from './utils/loadImage';
@@ -132,9 +132,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 (is.string(this.config.title) && !is.empty(this.config.title)) { if (is.string(this.config.title) && !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
@@ -216,8 +213,10 @@ const ui = {
toggleClass(this.elements.container, this.config.classNames.paused, this.paused); toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
// Set ARIA state // Set state
toggleState(this.elements.buttons.play, this.playing); Array.from(this.elements.buttons.play).forEach(target => {
target.pressed = this.playing;
});
// Only update controls on non timeupdate events // Only update controls on non timeupdate events
if (is.event(event) && event.type === 'timeupdate') { if (is.event(event) && event.type === 'timeupdate') {
-22
View File
@@ -283,25 +283,3 @@ export function trapFocus(element = null, toggle = false) {
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false); toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
} }
// Toggle aria-pressed state on a toggle button
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
export function toggleState(element, input) {
// If multiple elements passed
if (is.array(element) || is.nodeList(element)) {
Array.from(element).forEach(target => toggleState(target, input));
return;
}
// Bail if no target
if (!is.element(element)) {
return;
}
// Get state
const pressed = element.getAttribute('aria-pressed') === 'true';
const state = is.boolean(input) ? input : !pressed;
// Set the attribute on target
element.setAttribute('aria-pressed', state);
}
+1 -1
View File
@@ -32,5 +32,5 @@ export function formatTime(time = 0, displayHours = false, inverted = false) {
} }
// Render // Render
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
} }
+4 -4
View File
@@ -34,10 +34,10 @@
} }
// Change icons on state change // Change icons on state change
.plyr__control[aria-pressed='false'] .icon--pressed, .plyr__control:not(.plyr__control--pressed) .icon--pressed,
.plyr__control[aria-pressed='true'] .icon--not-pressed, .plyr__control.plyr__control--pressed .icon--not-pressed,
.plyr__control[aria-pressed='false'] .label--pressed, .plyr__control:not(.plyr__control--pressed) .label--pressed,
.plyr__control[aria-pressed='true'] .label--not-pressed { .plyr__control.plyr__control--pressed .label--not-pressed {
display: none; display: none;
} }