Merge pull request #1041 from sampotts/a11y-improvements
A11y improvements
This commit is contained in:
commit
3c9c1b4cdc
@ -21,7 +21,7 @@
|
||||
|
||||
Again, more changes from @friday!
|
||||
|
||||
- Restore window reference in `utils.is.cue()`
|
||||
- Restore window reference in `is.cue()`
|
||||
- Fix InvalidStateError and IE11 issues
|
||||
- Respect storage being disabled for storage getter
|
||||
|
||||
|
24
controls.md
24
controls.md
@ -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:
|
||||
|
||||
* `Array` of options (this builds the default controls based on your choices)
|
||||
* `String` containing the desired HTML
|
||||
* `Function` that will be executed and should return one of the above
|
||||
- `Array` of options (this builds the default controls based on your choices)
|
||||
- `String` containing the desired HTML
|
||||
- `Function` that will be executed and should return one of the above
|
||||
|
||||
## 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:
|
||||
|
||||
* `{id}` - the dynamically generated ID for the player (for form controls)
|
||||
* `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||
* `{title}` - the title of your media, if specified
|
||||
- `{id}` - the dynamically generated ID for the player (for form controls)
|
||||
- `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||
- `{title}` - the title of your media, if specified
|
||||
|
||||
### Limitations
|
||||
|
||||
* 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
|
||||
- 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
|
||||
|
||||
### Example
|
||||
|
||||
@ -105,7 +105,7 @@ const controls = `
|
||||
<svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg>
|
||||
<span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span>
|
||||
</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--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
|
||||
@ -122,7 +122,7 @@ const controls = `
|
||||
</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>
|
||||
<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--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
|
||||
@ -131,13 +131,13 @@ const controls = `
|
||||
<div class="plyr__volume">
|
||||
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume">
|
||||
</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--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--not-pressed plyr__tooltip" role="tooltip">Enable captions</span>
|
||||
</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--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>
|
||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
156
dist/plyr.js
vendored
156
dist/plyr.js
vendored
@ -595,30 +595,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
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 () {
|
||||
@ -1271,11 +1247,12 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
// 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 = {
|
||||
// Get icon URL
|
||||
getIconUrl: function getIconUrl() {
|
||||
@ -1289,8 +1266,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
},
|
||||
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
// Find the UI controls
|
||||
findElements: function findElements() {
|
||||
try {
|
||||
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
@ -1386,12 +1362,11 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
pip: 'PIP',
|
||||
airplay: 'AirPlay'
|
||||
};
|
||||
|
||||
var text = universals[type] || i18n.get(type, this.config);
|
||||
|
||||
var attributes = Object.assign({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
|
||||
});
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
|
||||
@ -1493,9 +1468,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Label/Tooltip
|
||||
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
|
||||
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
|
||||
|
||||
// Add aria attributes
|
||||
attributes['aria-pressed'] = false;
|
||||
} else {
|
||||
button.appendChild(controls.createIcon.call(this, icon));
|
||||
button.appendChild(controls.createLabel.call(this, label));
|
||||
@ -1517,19 +1489,26 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
// Create an <input type='range'>
|
||||
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
|
||||
var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
||||
type: 'range',
|
||||
@ -1540,7 +1519,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
autocomplete: 'off',
|
||||
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||
role: 'slider',
|
||||
'aria-labelledby': attributes.id + '-label',
|
||||
'aria-label': i18n.get(type, this.config),
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
'aria-valuenow': 0
|
||||
@ -1551,10 +1530,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Set the fill for webkit now
|
||||
controls.updateRangeFill.call(this, input);
|
||||
|
||||
return {
|
||||
label: label,
|
||||
input: input
|
||||
};
|
||||
return input;
|
||||
},
|
||||
|
||||
|
||||
@ -1576,7 +1552,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
played: 'played',
|
||||
buffer: 'buffered'
|
||||
}[type];
|
||||
|
||||
var suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
|
||||
|
||||
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
|
||||
updateTimeDisplay: function updateTimeDisplay() {
|
||||
var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
@ -1655,11 +1647,8 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always display hours if duration is over an hour
|
||||
var forceHours = getHours(this.duration) > 0;
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
|
||||
// Set aria value for https://github.com/sampotts/plyr/issues/905
|
||||
range.setAttribute('aria-valuenow', range.value);
|
||||
// Set aria values for https://github.com/sampotts/plyr/issues/905
|
||||
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
|
||||
if (!browser.isWebkit) {
|
||||
@ -1849,11 +1850,16 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Show the duration on metadataloaded or durationchange events
|
||||
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) {
|
||||
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
|
||||
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));
|
||||
|
||||
// Seek range slider
|
||||
var seek = controls.createRange.call(this, 'seek', {
|
||||
progress.appendChild(controls.createRange.call(this, 'seek', {
|
||||
id: 'plyr-seek-' + data.id
|
||||
});
|
||||
progress.appendChild(seek.label);
|
||||
progress.appendChild(seek.input);
|
||||
}));
|
||||
|
||||
// Buffer progress
|
||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
||||
@ -2433,11 +2437,9 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
};
|
||||
|
||||
// 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
|
||||
}));
|
||||
volume.appendChild(range.label);
|
||||
volume.appendChild(range.input);
|
||||
})));
|
||||
|
||||
this.elements.volume = volume;
|
||||
|
||||
@ -2702,7 +2704,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
Array.from(labels).forEach(function (label) {
|
||||
toggleClass(label, _this8.config.classNames.hidden, false);
|
||||
toggleClass(label, _this8.config.classNames.tooltip, true);
|
||||
label.setAttribute('role', 'tooltip');
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2977,7 +2978,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
toggleState(this.elements.buttons.captions, active);
|
||||
this.elements.buttons.captions.pressed = active;
|
||||
|
||||
// Add class hook
|
||||
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)
|
||||
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
|
||||
seekTime: 10,
|
||||
|
||||
@ -3332,6 +3337,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
pause: 'Pause',
|
||||
fastForward: 'Forward {seektime}s',
|
||||
seek: 'Seek',
|
||||
seekLabel: '{currentTime} of {duration}',
|
||||
played: 'Played',
|
||||
buffered: 'Buffered',
|
||||
currentTime: 'Current time',
|
||||
@ -3346,6 +3352,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
quality: 'Quality',
|
||||
@ -3475,6 +3482,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
posterEnabled: 'plyr__poster-enabled',
|
||||
ads: 'plyr__ads',
|
||||
control: 'plyr__control',
|
||||
controlPressed: 'plyr__control--pressed',
|
||||
playing: 'plyr--playing',
|
||||
paused: 'plyr--paused',
|
||||
stopped: 'plyr--stopped',
|
||||
@ -3616,7 +3624,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Update toggle button
|
||||
var button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
toggleState(button, this.active);
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// 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 (is.string(this.config.title) && !is.empty(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
|
||||
@ -4068,13 +4073,17 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Check playing state
|
||||
checkPlaying: function checkPlaying(event) {
|
||||
var _this3 = this;
|
||||
|
||||
// Class hooks
|
||||
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.stopped, this.stopped);
|
||||
|
||||
// Set ARIA state
|
||||
toggleState(this.elements.buttons.play, this.playing);
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play).forEach(function (target) {
|
||||
target.pressed = _this3.playing;
|
||||
});
|
||||
|
||||
// Only update controls on non timeupdate events
|
||||
if (is.event(event) && event.type === 'timeupdate') {
|
||||
@ -4088,7 +4097,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Check if media is loading
|
||||
checkLoading: function checkLoading(event) {
|
||||
var _this3 = this;
|
||||
var _this4 = this;
|
||||
|
||||
this.loading = ['stalled', 'waiting'].includes(event.type);
|
||||
|
||||
@ -4098,10 +4107,10 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Timer to prevent flicker when seeking
|
||||
this.timers.loading = setTimeout(function () {
|
||||
// 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
|
||||
ui.toggleControls.call(_this3);
|
||||
ui.toggleControls.call(_this4);
|
||||
}, this.loading ? 250 : 0);
|
||||
},
|
||||
|
||||
@ -7131,9 +7140,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
wrap(this.media, this.elements.container);
|
||||
}
|
||||
|
||||
// Allow focus to be captured
|
||||
this.elements.container.setAttribute('tabindex', 0);
|
||||
|
||||
// Add style hook
|
||||
ui.addStyleHook.call(this);
|
||||
|
||||
|
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js
vendored
2
dist/plyr.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js.map
vendored
2
dist/plyr.min.js.map
vendored
File diff suppressed because one or more lines are too long
156
dist/plyr.polyfilled.js
vendored
156
dist/plyr.polyfilled.js
vendored
@ -5981,30 +5981,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
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 () {
|
||||
@ -6657,11 +6633,12 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
// 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 = {
|
||||
// Get icon URL
|
||||
getIconUrl: function getIconUrl() {
|
||||
@ -6675,8 +6652,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
},
|
||||
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
// Find the UI controls
|
||||
findElements: function findElements() {
|
||||
try {
|
||||
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
@ -6772,12 +6748,11 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
pip: 'PIP',
|
||||
airplay: 'AirPlay'
|
||||
};
|
||||
|
||||
var text = universals[type] || i18n.get(type, this.config);
|
||||
|
||||
var attributes = Object.assign({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
|
||||
});
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
|
||||
@ -6879,9 +6854,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Label/Tooltip
|
||||
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
|
||||
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
|
||||
|
||||
// Add aria attributes
|
||||
attributes['aria-pressed'] = false;
|
||||
} else {
|
||||
button.appendChild(controls.createIcon.call(this, icon));
|
||||
button.appendChild(controls.createLabel.call(this, label));
|
||||
@ -6903,19 +6875,26 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
// Create an <input type='range'>
|
||||
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
|
||||
var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
||||
type: 'range',
|
||||
@ -6926,7 +6905,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
autocomplete: 'off',
|
||||
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||
role: 'slider',
|
||||
'aria-labelledby': attributes.id + '-label',
|
||||
'aria-label': i18n.get(type, this.config),
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
'aria-valuenow': 0
|
||||
@ -6937,10 +6916,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Set the fill for webkit now
|
||||
controls.updateRangeFill.call(this, input);
|
||||
|
||||
return {
|
||||
label: label,
|
||||
input: input
|
||||
};
|
||||
return input;
|
||||
},
|
||||
|
||||
|
||||
@ -6962,7 +6938,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
played: 'played',
|
||||
buffer: 'buffered'
|
||||
}[type];
|
||||
|
||||
var suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
|
||||
|
||||
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
|
||||
updateTimeDisplay: function updateTimeDisplay() {
|
||||
var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
@ -7041,11 +7033,8 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always display hours if duration is over an hour
|
||||
var forceHours = getHours(this.duration) > 0;
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
|
||||
// Set aria value for https://github.com/sampotts/plyr/issues/905
|
||||
range.setAttribute('aria-valuenow', range.value);
|
||||
// Set aria values for https://github.com/sampotts/plyr/issues/905
|
||||
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
|
||||
if (!browser.isWebkit) {
|
||||
@ -7235,11 +7236,16 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Show the duration on metadataloaded or durationchange events
|
||||
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) {
|
||||
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
|
||||
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));
|
||||
|
||||
// Seek range slider
|
||||
var seek = controls.createRange.call(this, 'seek', {
|
||||
progress.appendChild(controls.createRange.call(this, 'seek', {
|
||||
id: 'plyr-seek-' + data.id
|
||||
});
|
||||
progress.appendChild(seek.label);
|
||||
progress.appendChild(seek.input);
|
||||
}));
|
||||
|
||||
// Buffer progress
|
||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
||||
@ -7819,11 +7823,9 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
};
|
||||
|
||||
// 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
|
||||
}));
|
||||
volume.appendChild(range.label);
|
||||
volume.appendChild(range.input);
|
||||
})));
|
||||
|
||||
this.elements.volume = volume;
|
||||
|
||||
@ -8088,7 +8090,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
Array.from(labels).forEach(function (label) {
|
||||
toggleClass(label, _this8.config.classNames.hidden, false);
|
||||
toggleClass(label, _this8.config.classNames.tooltip, true);
|
||||
label.setAttribute('role', 'tooltip');
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -8363,7 +8364,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
toggleState(this.elements.buttons.captions, active);
|
||||
this.elements.buttons.captions.pressed = active;
|
||||
|
||||
// Add class hook
|
||||
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)
|
||||
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
|
||||
seekTime: 10,
|
||||
|
||||
@ -8718,6 +8723,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
pause: 'Pause',
|
||||
fastForward: 'Forward {seektime}s',
|
||||
seek: 'Seek',
|
||||
seekLabel: '{currentTime} of {duration}',
|
||||
played: 'Played',
|
||||
buffered: 'Buffered',
|
||||
currentTime: 'Current time',
|
||||
@ -8732,6 +8738,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
quality: 'Quality',
|
||||
@ -8861,6 +8868,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
posterEnabled: 'plyr__poster-enabled',
|
||||
ads: 'plyr__ads',
|
||||
control: 'plyr__control',
|
||||
controlPressed: 'plyr__control--pressed',
|
||||
playing: 'plyr--playing',
|
||||
paused: 'plyr--paused',
|
||||
stopped: 'plyr--stopped',
|
||||
@ -9002,7 +9010,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Update toggle button
|
||||
var button = this.player.elements.buttons.fullscreen;
|
||||
if (is$1.element(button)) {
|
||||
toggleState(button, this.active);
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// 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 (is$1.string(this.config.title) && !is$1.empty(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
|
||||
@ -9454,13 +9459,17 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Check playing state
|
||||
checkPlaying: function checkPlaying(event) {
|
||||
var _this3 = this;
|
||||
|
||||
// Class hooks
|
||||
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.stopped, this.stopped);
|
||||
|
||||
// Set ARIA state
|
||||
toggleState(this.elements.buttons.play, this.playing);
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play).forEach(function (target) {
|
||||
target.pressed = _this3.playing;
|
||||
});
|
||||
|
||||
// Only update controls on non timeupdate events
|
||||
if (is$1.event(event) && event.type === 'timeupdate') {
|
||||
@ -9474,7 +9483,7 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
|
||||
// Check if media is loading
|
||||
checkLoading: function checkLoading(event) {
|
||||
var _this3 = this;
|
||||
var _this4 = this;
|
||||
|
||||
this.loading = ['stalled', 'waiting'].includes(event.type);
|
||||
|
||||
@ -9484,10 +9493,10 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
// Timer to prevent flicker when seeking
|
||||
this.timers.loading = setTimeout(function () {
|
||||
// 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
|
||||
ui.toggleControls.call(_this3);
|
||||
ui.toggleControls.call(_this4);
|
||||
}, this.loading ? 250 : 0);
|
||||
},
|
||||
|
||||
@ -12511,9 +12520,6 @@ typeof navigator === "object" && (function (global, factory) {
|
||||
wrap$2(this.media, this.elements.container);
|
||||
}
|
||||
|
||||
// Allow focus to be captured
|
||||
this.elements.container.setAttribute('tabindex', 0);
|
||||
|
||||
// Add style hook
|
||||
ui.addStyleHook.call(this);
|
||||
|
||||
|
2
dist/plyr.polyfilled.js.map
vendored
2
dist/plyr.polyfilled.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
2
dist/plyr.polyfilled.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js.map
vendored
2
dist/plyr.polyfilled.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -15,7 +15,6 @@ import {
|
||||
insertAfter,
|
||||
removeElement,
|
||||
toggleClass,
|
||||
toggleState,
|
||||
} from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
import fetch from './utils/fetch';
|
||||
@ -193,7 +192,7 @@ const captions = {
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
toggleState(this.elements.buttons.captions, active);
|
||||
this.elements.buttons.captions.pressed = active;
|
||||
|
||||
// Add class hook
|
||||
toggleClass(this.elements.container, activeClass, active);
|
||||
|
@ -18,6 +18,10 @@ const defaults = {
|
||||
// Only allow one media playing at once (vimeo only)
|
||||
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
|
||||
seekTime: 10,
|
||||
|
||||
@ -153,6 +157,7 @@ const defaults = {
|
||||
pause: 'Pause',
|
||||
fastForward: 'Forward {seektime}s',
|
||||
seek: 'Seek',
|
||||
seekLabel: '{currentTime} of {duration}',
|
||||
played: 'Played',
|
||||
buffered: 'Buffered',
|
||||
currentTime: 'Current time',
|
||||
@ -167,6 +172,7 @@ const defaults = {
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
menuBack: 'Go back to previous menu',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
quality: 'Quality',
|
||||
@ -334,6 +340,7 @@ const defaults = {
|
||||
posterEnabled: 'plyr__poster-enabled',
|
||||
ads: 'plyr__ads',
|
||||
control: 'plyr__control',
|
||||
controlPressed: 'plyr__control--pressed',
|
||||
playing: 'plyr--playing',
|
||||
paused: 'plyr--paused',
|
||||
stopped: 'plyr--stopped',
|
||||
|
116
src/js/controls.js
vendored
116
src/js/controls.js
vendored
@ -20,7 +20,7 @@ import {
|
||||
setAttributes,
|
||||
toggleClass,
|
||||
toggleHidden,
|
||||
toggleState,
|
||||
matches,
|
||||
} from './utils/elements';
|
||||
import { off, on } from './utils/events';
|
||||
import is from './utils/is';
|
||||
@ -29,6 +29,7 @@ import { extend } from './utils/objects';
|
||||
import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
|
||||
import { formatTime, getHours } from './utils/time';
|
||||
|
||||
// TODO: Don't export a massive object - break down and create class
|
||||
const controls = {
|
||||
// Get icon URL
|
||||
getIconUrl() {
|
||||
@ -41,8 +42,7 @@ const controls = {
|
||||
};
|
||||
},
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
// Find the UI controls
|
||||
findElements() {
|
||||
try {
|
||||
this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
@ -139,12 +139,11 @@ const controls = {
|
||||
pip: 'PIP',
|
||||
airplay: 'AirPlay',
|
||||
};
|
||||
|
||||
const text = universals[type] || i18n.get(type, this.config);
|
||||
|
||||
const attributes = Object.assign({}, attr, {
|
||||
class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' '),
|
||||
});
|
||||
|
||||
return createElement('span', attributes, text);
|
||||
},
|
||||
|
||||
@ -250,9 +249,6 @@ const controls = {
|
||||
// Label/Tooltip
|
||||
button.appendChild(controls.createLabel.call(this, labelPressed, { class: 'label--pressed' }));
|
||||
button.appendChild(controls.createLabel.call(this, label, { class: 'label--not-pressed' }));
|
||||
|
||||
// Add aria attributes
|
||||
attributes['aria-pressed'] = false;
|
||||
} else {
|
||||
button.appendChild(controls.createIcon.call(this, icon));
|
||||
button.appendChild(controls.createLabel.call(this, label));
|
||||
@ -274,22 +270,23 @@ const controls = {
|
||||
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;
|
||||
},
|
||||
|
||||
// Create an <input type='range'>
|
||||
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
|
||||
const input = createElement(
|
||||
'input',
|
||||
@ -304,7 +301,7 @@ const controls = {
|
||||
autocomplete: 'off',
|
||||
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||
role: 'slider',
|
||||
'aria-labelledby': `${attributes.id}-label`,
|
||||
'aria-label': i18n.get(type, this.config),
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
'aria-valuenow': 0,
|
||||
@ -318,10 +315,7 @@ const controls = {
|
||||
// Set the fill for webkit now
|
||||
controls.updateRangeFill.call(this, input);
|
||||
|
||||
return {
|
||||
label,
|
||||
input,
|
||||
};
|
||||
return input;
|
||||
},
|
||||
|
||||
// Create a <progress>
|
||||
@ -349,7 +343,6 @@ const controls = {
|
||||
played: 'played',
|
||||
buffer: 'buffered',
|
||||
}[type];
|
||||
|
||||
const suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
|
||||
|
||||
progress.innerText = `% ${suffix.toLowerCase()}`;
|
||||
@ -412,6 +405,19 @@ const controls = {
|
||||
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
|
||||
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
||||
// Bail if there's no element to display or the value isn't a number
|
||||
@ -419,11 +425,8 @@ const controls = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always display hours if duration is over an hour
|
||||
const forceHours = getHours(this.duration) > 0;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
target.innerText = formatTime(time, forceHours, inverted);
|
||||
target.innerText = controls.formatTime(time, inverted);
|
||||
},
|
||||
|
||||
// Update volume UI and storage
|
||||
@ -439,7 +442,7 @@ const controls = {
|
||||
|
||||
// Update mute state
|
||||
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;
|
||||
}
|
||||
|
||||
// Set aria value for https://github.com/sampotts/plyr/issues/905
|
||||
range.setAttribute('aria-valuenow', range.value);
|
||||
// Set aria values for https://github.com/sampotts/plyr/issues/905
|
||||
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
|
||||
if (!browser.isWebkit) {
|
||||
@ -610,11 +628,16 @@ const controls = {
|
||||
|
||||
// Show the duration on metadataloaded or durationchange events
|
||||
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)) {
|
||||
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
|
||||
const hasDuration = is.element(this.elements.display.duration);
|
||||
|
||||
@ -1117,11 +1140,11 @@ const controls = {
|
||||
const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
|
||||
|
||||
// Seek range slider
|
||||
const seek = controls.createRange.call(this, 'seek', {
|
||||
id: `plyr-seek-${data.id}`,
|
||||
});
|
||||
progress.appendChild(seek.label);
|
||||
progress.appendChild(seek.input);
|
||||
progress.appendChild(
|
||||
controls.createRange.call(this, 'seek', {
|
||||
id: `plyr-seek-${data.id}`,
|
||||
}),
|
||||
);
|
||||
|
||||
// Buffer progress
|
||||
progress.appendChild(controls.createProgress.call(this, 'buffer'));
|
||||
@ -1175,15 +1198,15 @@ const controls = {
|
||||
};
|
||||
|
||||
// Create the volume range slider
|
||||
const range = controls.createRange.call(
|
||||
this,
|
||||
'volume',
|
||||
extend(attributes, {
|
||||
id: `plyr-volume-${data.id}`,
|
||||
}),
|
||||
volume.appendChild(
|
||||
controls.createRange.call(
|
||||
this,
|
||||
'volume',
|
||||
extend(attributes, {
|
||||
id: `plyr-volume-${data.id}`,
|
||||
}),
|
||||
),
|
||||
);
|
||||
volume.appendChild(range.label);
|
||||
volume.appendChild(range.input);
|
||||
|
||||
this.elements.volume = volume;
|
||||
|
||||
@ -1448,7 +1471,6 @@ const controls = {
|
||||
Array.from(labels).forEach(label => {
|
||||
toggleClass(label, this.config.classNames.hidden, false);
|
||||
toggleClass(label, this.config.classNames.tooltip, true);
|
||||
label.setAttribute('role', 'tooltip');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
// ==========================================================================
|
||||
|
||||
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 is from './utils/is';
|
||||
|
||||
@ -16,7 +16,7 @@ function onChange() {
|
||||
// Update toggle button
|
||||
const button = this.player.elements.buttons.fullscreen;
|
||||
if (is.element(button)) {
|
||||
toggleState(button, this.active);
|
||||
button.pressed = this.active;
|
||||
}
|
||||
|
||||
// Trigger an event
|
||||
|
@ -263,9 +263,6 @@ class Plyr {
|
||||
wrap(this.media, this.elements.container);
|
||||
}
|
||||
|
||||
// Allow focus to be captured
|
||||
this.elements.container.setAttribute('tabindex', 0);
|
||||
|
||||
// Add style hook
|
||||
ui.addStyleHook.call(this);
|
||||
|
||||
|
11
src/js/ui.js
11
src/js/ui.js
@ -7,7 +7,7 @@ import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
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 is from './utils/is';
|
||||
import loadImage from './utils/loadImage';
|
||||
@ -132,9 +132,6 @@ const ui = {
|
||||
// If there's a media title set, use that for the label
|
||||
if (is.string(this.config.title) && !is.empty(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
|
||||
@ -216,8 +213,10 @@ const ui = {
|
||||
toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
||||
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
||||
|
||||
// Set ARIA state
|
||||
toggleState(this.elements.buttons.play, this.playing);
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play).forEach(target => {
|
||||
target.pressed = this.playing;
|
||||
});
|
||||
|
||||
// Only update controls on non timeupdate events
|
||||
if (is.event(event) && event.type === 'timeupdate') {
|
||||
|
@ -283,25 +283,3 @@ export function trapFocus(element = null, 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);
|
||||
}
|
||||
|
@ -32,5 +32,5 @@ export function formatTime(time = 0, displayHours = false, inverted = false) {
|
||||
}
|
||||
|
||||
// Render
|
||||
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||
return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||
}
|
||||
|
@ -34,10 +34,10 @@
|
||||
}
|
||||
|
||||
// Change icons on state change
|
||||
.plyr__control[aria-pressed='false'] .icon--pressed,
|
||||
.plyr__control[aria-pressed='true'] .icon--not-pressed,
|
||||
.plyr__control[aria-pressed='false'] .label--pressed,
|
||||
.plyr__control[aria-pressed='true'] .label--not-pressed {
|
||||
.plyr__control:not(.plyr__control--pressed) .icon--pressed,
|
||||
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
||||
.plyr__control:not(.plyr__control--pressed) .label--pressed,
|
||||
.plyr__control.plyr__control--pressed .label--not-pressed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user