From 90c5735904354f5fde0dcdae9f8894fe9088739c Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 28 May 2018 10:19:07 +1000 Subject: WIP --- src/js/captions.js | 2 +- src/js/controls.js | 47 ++++++++++++++++------------------------ src/js/defaults.js | 5 +++++ src/js/fullscreen.js | 2 +- src/js/plyr.js | 2 +- src/js/ui.js | 25 +++++++++++---------- src/js/utils.js | 22 ------------------- src/sass/components/control.scss | 8 +++---- 8 files changed, 44 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/js/captions.js b/src/js/captions.js index df717351..fadab43f 100644 --- a/src/js/captions.js +++ b/src/js/captions.js @@ -262,7 +262,7 @@ const captions = { if (active) { utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true); - utils.toggleState(this.elements.buttons.captions, true); + this.elements.buttons.captions.pressed = true; } }, }; diff --git a/src/js/controls.js b/src/js/controls.js index c76bd66b..fc000b52 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -243,9 +243,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)); @@ -267,22 +264,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 utils.hasClass(button, className); + }, + set(pressed = false) { + utils.toggleClass(button, className, pressed); + }, + }); + return button; }, // Create an createRange(type, attributes) { - // Seek label - const label = utils.createElement( - 'label', - { - for: attributes.id, - id: `${attributes.id}-label`, - class: this.config.classNames.hidden, - }, - i18n.get(type, this.config), - ); - // Seek input const input = utils.createElement( 'input', @@ -297,7 +295,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, @@ -311,10 +309,7 @@ const controls = { // Set the fill for webkit now controls.updateRangeFill.call(this, input); - return { - label, - input, - }; + return input; }, // Create a @@ -435,7 +430,7 @@ const controls = { // Update mute state if (utils.is.element(this.elements.buttons.mute)) { - utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); + this.elements.buttons.mute.pressed = this.muted || this.volume === 0; } }, @@ -1149,11 +1144,9 @@ const controls = { const progress = utils.createElement('div', utils.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')); @@ -1207,15 +1200,13 @@ const controls = { }; // Create the volume range slider - const range = controls.createRange.call( + volume.appendChild(controls.createRange.call( this, 'volume', utils.extend(attributes, { id: `plyr-volume-${data.id}`, }), - ); - volume.appendChild(range.label); - volume.appendChild(range.input); + )); this.elements.volume = volume; diff --git a/src/js/defaults.js b/src/js/defaults.js index 5b1a4dd3..54c19f94 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -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, @@ -334,6 +338,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', diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 000ba706..cc91d1a4 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -15,7 +15,7 @@ function onChange() { // Update toggle button const button = this.player.elements.buttons.fullscreen; if (utils.is.element(button)) { - utils.toggleState(button, this.active); + button.pressed = this.active; } // Trigger an event diff --git a/src/js/plyr.js b/src/js/plyr.js index 4c984fd7..2cf5d58d 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -854,7 +854,7 @@ class Plyr { this.captions.active = show; // Toggle state - utils.toggleState(this.elements.buttons.captions, this.captions.active); + this.elements.buttons.captions.pressed = this.captions.active; // Add class hook utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active); diff --git a/src/js/ui.js b/src/js/ui.js index 3a8f2d05..5b14e2fe 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -164,17 +164,16 @@ const ui = { } // Load the image, and set poster if successful - const loadPromise = utils.loadImage(poster) - .then(() => { - this.elements.poster.style.backgroundImage = `url('${poster}')`; - Object.assign(this.elements.poster.style, { - backgroundImage: `url('${poster}')`, - // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube) - backgroundSize: '', - }); - ui.togglePoster.call(this, true); - return poster; + const loadPromise = utils.loadImage(poster).then(() => { + this.elements.poster.style.backgroundImage = `url('${poster}')`; + Object.assign(this.elements.poster.style, { + backgroundImage: `url('${poster}')`, + // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube) + backgroundSize: '', }); + ui.togglePoster.call(this, true); + return poster; + }); // Hide the element if the poster can't be loaded (otherwise it will just be a black element covering the video) loadPromise.catch(() => ui.togglePoster.call(this, false)); @@ -190,8 +189,10 @@ const ui = { utils.toggleClass(this.elements.container, this.config.classNames.paused, this.paused); utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); - // Set ARIA state - utils.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 (utils.is.event(event) && event.type === 'timeupdate') { diff --git a/src/js/utils.js b/src/js/utils.js index 0c5a28d7..201c06c8 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -572,28 +572,6 @@ const utils = { element.dispatchEvent(event); }, - // Toggle aria-pressed state on a toggle button - // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles - toggleState(element, input) { - // If multiple elements passed - if (utils.is.array(element) || utils.is.nodeList(element)) { - Array.from(element).forEach(target => utils.toggleState(target, input)); - return; - } - - // Bail if no target - if (!utils.is.element(element)) { - return; - } - - // Get state - const pressed = element.getAttribute('aria-pressed') === 'true'; - const state = utils.is.boolean(input) ? input : !pressed; - - // Set the attribute on target - element.setAttribute('aria-pressed', state); - }, - // Format string format(input, ...args) { if (utils.is.empty(input)) { diff --git a/src/sass/components/control.scss b/src/sass/components/control.scss index 52716805..cfef1b3a 100644 --- a/src/sass/components/control.scss +++ b/src/sass/components/control.scss @@ -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; } -- cgit v1.2.3 From 38f10d4cc67b3109189699f7e65189a852064236 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 11 Jun 2018 16:19:11 +1000 Subject: WIP --- src/js/controls.js | 80 ++++++++++++++++++++++++++++++++++++++---------------- src/js/defaults.js | 2 ++ src/js/plyr.js | 5 +--- src/js/ui.js | 3 -- 4 files changed, 59 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/js/controls.js b/src/js/controls.js index e2b4ed1a..f39101af 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -12,8 +12,6 @@ import utils from './utils'; const browser = utils.getBrowser(); const controls = { - - // Get icon URL getIconUrl() { const url = new URL(this.config.iconUrl, window.location); @@ -359,10 +357,14 @@ const controls = { createTime(type) { const attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]); - const container = utils.createElement('div', utils.extend(attributes, { - class: `plyr__time ${attributes.class}`, - 'aria-label': i18n.get(type, this.config), - }), '00:00'); + const container = utils.createElement( + 'div', + utils.extend(attributes, { + class: `plyr__time ${attributes.class}`, + 'aria-label': i18n.get(type, this.config), + }), + '00:00', + ); // Reference for updates this.elements.display[type] = container; @@ -403,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 (!utils.is.number(time)) { + return time; + } + + // Always display hours if duration is over an hour + const forceHours = utils.getHours(this.duration) > 0; + + return utils.formatTime(time, forceHours, inverted); + }, + // Update the displayed time updateTimeDisplay(target = null, time = 0, inverted = false) { // Bail if there's no element to display or the value isn't a number @@ -410,11 +425,8 @@ const controls = { return; } - // Always display hours if duration is over an hour - const forceHours = utils.getHours(this.duration) > 0; - // eslint-disable-next-line no-param-reassign - target.innerText = utils.formatTime(time, forceHours, inverted); + target.innerText = controls.formatTime(time, inverted); }, // Update volume UI and storage @@ -509,8 +521,20 @@ 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 (utils.matches(range, this.config.selectors.inputs.seek)) { + range.setAttribute('aria-valuenow', this.currentTime); + const currentTime = controls.formatTime(this.currentTime); + const duration = controls.formatTime(this.duration); + const format = i18n.get('seekLabel', this.config); + range.setAttribute('aria-valuetext', format.replace('{currentTime}', currentTime).replace('{duration}', duration)); + } else if (utils.matches(range, this.config.selectors.inputs.volume)) { + const percent = range.value * 100; + range.setAttribute('aria-valuenow', percent); + range.setAttribute('aria-valuetext', `${percent}%`); + } else { + range.setAttribute('aria-valuenow', range.value); + } // WebKit only if (!browser.isWebkit) { @@ -599,11 +623,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 (utils.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 = utils.is.element(this.elements.display.duration); @@ -1126,9 +1155,11 @@ const controls = { const progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress)); // Seek range slider - progress.appendChild(controls.createRange.call(this, 'seek', { - id: `plyr-seek-${data.id}`, - })); + progress.appendChild( + controls.createRange.call(this, 'seek', { + id: `plyr-seek-${data.id}`, + }), + ); // Buffer progress progress.appendChild(controls.createProgress.call(this, 'buffer')); @@ -1182,13 +1213,15 @@ const controls = { }; // Create the volume range slider - volume.appendChild(controls.createRange.call( - this, - 'volume', - utils.extend(attributes, { - id: `plyr-volume-${data.id}`, - }), - )); + volume.appendChild( + controls.createRange.call( + this, + 'volume', + utils.extend(attributes, { + id: `plyr-volume-${data.id}`, + }), + ), + ); this.elements.volume = volume; @@ -1463,7 +1496,6 @@ const controls = { Array.from(labels).forEach(label => { utils.toggleClass(label, this.config.classNames.hidden, false); utils.toggleClass(label, this.config.classNames.tooltip, true); - label.setAttribute('role', 'tooltip'); }); } }, diff --git a/src/js/defaults.js b/src/js/defaults.js index 78371d68..9026ab18 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -169,6 +169,7 @@ const defaults = { pause: 'Pause', fastForward: 'Forward {seektime}s', seek: 'Seek', + seekLabel: '{currentTime} of {duration}', played: 'Played', buffered: 'Buffered', currentTime: 'Current time', @@ -183,6 +184,7 @@ const defaults = { frameTitle: 'Player for {title}', captions: 'Captions', settings: 'Settings', + menuBack: 'Go back to previous menu', speed: 'Speed', normal: 'Normal', quality: 'Quality', diff --git a/src/js/plyr.js b/src/js/plyr.js index ce3d3be5..65f24239 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -260,9 +260,6 @@ class Plyr { utils.wrap(this.media, this.elements.container); } - // Allow focus to be captured - this.elements.container.setAttribute('tabindex', 0); - // Add style hook ui.addStyleHook.call(this); @@ -849,7 +846,7 @@ class Plyr { // Update state and trigger event if (active !== this.captions.active) { this.captions.active = active; - utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); + utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled'); } } diff --git a/src/js/ui.js b/src/js/ui.js index e90a1492..979d8341 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -127,9 +127,6 @@ const ui = { // If there's a media title set, use that for the label if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) { label += `, ${this.config.title}`; - - // Set container label - this.elements.container.setAttribute('aria-label', this.config.title); } // If there's a play button, set label -- cgit v1.2.3 From 599883e684cf72a631ea366d0cb821fcb1c3d013 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 17 Jun 2018 01:30:24 +1000 Subject: Formatting fix --- src/js/fullscreen.js | 4 ++-- src/js/plyr.js | 44 ++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index d9cba17f..7091fde1 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -68,8 +68,8 @@ class Fullscreen { document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => { - // TODO: Filter for target?? - onChange.call(this); + // TODO: Filter for target?? + onChange.call(this); }, ); diff --git a/src/js/plyr.js b/src/js/plyr.js index 93fccbfa..d4b63874 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -828,7 +828,7 @@ class Plyr { */ toggleCaptions(input) { captions.toggle.call(this, input, false); - } + } /** * Set the caption track by index @@ -1032,35 +1032,35 @@ class Plyr { // Provider specific stuff if (this.isHTML5) { - // Clear timeout - clearTimeout(this.timers.loading); + // Clear timeout + clearTimeout(this.timers.loading); - // Restore native video controls - ui.toggleNativeControls.call(this, true); + // Restore native video controls + ui.toggleNativeControls.call(this, true); - // Clean up - done(); + // Clean up + done(); } else if (this.isYouTube) { - // Clear timers - clearInterval(this.timers.buffering); - clearInterval(this.timers.playing); + // Clear timers + clearInterval(this.timers.buffering); + clearInterval(this.timers.playing); - // Destroy YouTube API + // Destroy YouTube API if (this.embed !== null && is.function(this.embed.destroy)) { - this.embed.destroy(); - } + this.embed.destroy(); + } - // Clean up - done(); + // Clean up + done(); } else if (this.isVimeo) { - // Destroy Vimeo API - // then clean up (wait, to prevent postmessage errors) - if (this.embed !== null) { - this.embed.unload().then(done); - } + // Destroy Vimeo API + // then clean up (wait, to prevent postmessage errors) + if (this.embed !== null) { + this.embed.unload().then(done); + } - // Vimeo does not always return - setTimeout(done, 200); + // Vimeo does not always return + setTimeout(done, 200); } } -- cgit v1.2.3