Merge pull request #1041 from sampotts/a11y-improvements
A11y improvements
This commit is contained in:
		| @ -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
											
										
									
								
							
							
								
								
									
										154
									
								
								dist/plyr.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										154
									
								
								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 | ||||
|             // 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
											
										
									
								
							
							
								
								
									
										154
									
								
								dist/plyr.polyfilled.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										154
									
								
								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 | ||||
| 	        // 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', | ||||
|  | ||||
							
								
								
									
										102
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										102
									
								
								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 | ||||
|         // 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', { | ||||
|             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')); | ||||
| @ -1175,15 +1198,15 @@ const controls = { | ||||
|             }; | ||||
|  | ||||
|             // Create the volume range slider | ||||
|             const range = controls.createRange.call( | ||||
|             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; | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user