From dfe45d5bd6bbe2ff0b8393d4d8dce7cf4a4f0375 Mon Sep 17 00:00:00 2001 From: jmclaus Date: Fri, 27 Sep 2013 19:21:43 +0200 Subject: [PATCH 01/10] First pass at ARIA. Video player buttons announce themselves as button when used with a screen reader --- common/lib/xmodule/xmodule/js/src/video/04_video_control.js | 4 ++++ .../xmodule/xmodule/js/src/video/05_video_quality_control.js | 3 +++ .../xmodule/xmodule/js/src/video/07_video_volume_control.js | 3 +++ .../xmodule/xmodule/js/src/video/08_video_speed_control.js | 3 +++ common/lib/xmodule/xmodule/js/src/video/09_video_caption.js | 3 +++ 5 files changed, 16 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js index 5cdb5c7536..1e1450dc54 100644 --- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js @@ -63,6 +63,10 @@ function () { state.videoControl.el.addClass('html5'); state.controlHideTimeout = setTimeout(state.videoControl.hideControls, state.videoControl.fadeOutTimeout); } + // ARIA + // Let screen readers know these anchors behaves like a button. + state.videoControl.playPauseEl.attr('role', gettext('button')); + state.videoControl.fullScreenEl.attr('role', gettext('button')); } // function _bindHandlers(state) diff --git a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js index cff103468d..e996ce5213 100644 --- a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js @@ -43,6 +43,9 @@ function () { state.videoQualityControl.el.show(); state.videoQualityControl.quality = null; + // ARIA + // Let screen readers know this anchor behaves like a button. + state.videoQualityControl.el.attr('role', gettext('button')); } // function _bindHandlers(state) diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index d8398ab530..3f31ddc75e 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -62,6 +62,9 @@ function () { }); state.videoVolumeControl.el.toggleClass('muted', state.videoVolumeControl.currentVolume === 0); + // ARIA + // Let screen readers know this anchor behaves like a button. + state.videoVolumeControl.buttonEl.attr('role', gettext('button')); } /** diff --git a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js index 67f62edf95..4b9f532e43 100644 --- a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js @@ -79,6 +79,9 @@ function () { }); state.videoSpeedControl.setSpeed(state.speed); + // ARIA + // Let screen readers know this anchor behaves like a button. + state.videoSpeedControl.el.children('a').attr('role', gettext('button')); } /** diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 825ed7b935..57bb2a38d1 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -105,6 +105,9 @@ function () { this.videoCaption.hideCaptions(true); this.videoCaption.hideSubtitlesEl.hide(); } + // ARIA + // Let screen readers know this anchor behaves like a button. + this.videoCaption.hideSubtitlesEl.attr('role', gettext('button')); } // function bindHandlers() From ce976a80c81099eae898b66c94f037d2f520e7b0 Mon Sep 17 00:00:00 2001 From: jmclaus Date: Sat, 28 Sep 2013 02:04:36 +0200 Subject: [PATCH 02/10] All buttons have an ARIA role, name , and state --- .../xmodule/js/src/video/04_video_control.js | 13 ++++++++++++- .../js/src/video/05_video_quality_control.js | 11 ++++++++++- .../xmodule/js/src/video/07_video_volume_control.js | 11 ++++++++++- .../xmodule/js/src/video/08_video_speed_control.js | 11 ++++++++++- .../xmodule/js/src/video/09_video_caption.js | 13 +++++++++++-- lms/templates/video.html | 2 +- 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js index 1e1450dc54..f31ed1caee 100644 --- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js @@ -63,10 +63,21 @@ function () { state.videoControl.el.addClass('html5'); state.controlHideTimeout = setTimeout(state.videoControl.hideControls, state.videoControl.fadeOutTimeout); } + // ARIA - // Let screen readers know these anchors behaves like a button. + // Let screen readers know that: + + // these anchors behaves like buttons state.videoControl.playPauseEl.attr('role', gettext('button')); state.videoControl.fullScreenEl.attr('role', gettext('button')); + + // what their names are: (title attribute are set in video.html template): + // Play, Pause + // Fill browser + + // what their states are: + state.videoControl.playPauseEl.attr('aria-disabled', 'false'); + state.videoControl.fullScreenEl.attr('aria-disabled', 'false'); } // function _bindHandlers(state) diff --git a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js index e996ce5213..5788cd6291 100644 --- a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js @@ -43,9 +43,18 @@ function () { state.videoQualityControl.el.show(); state.videoQualityControl.quality = null; + // ARIA - // Let screen readers know this anchor behaves like a button. + // Let screen readers know that: + + // this anchor behaves like a button state.videoQualityControl.el.attr('role', gettext('button')); + + // what its name is: (title attribute is set in video.html template): + // HD + + // what its state is: + state.videoQualityControl.el.attr('aria-disabled', 'false'); } // function _bindHandlers(state) diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index 3f31ddc75e..9eda8eade7 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -62,9 +62,18 @@ function () { }); state.videoVolumeControl.el.toggleClass('muted', state.videoVolumeControl.currentVolume === 0); + // ARIA - // Let screen readers know this anchor behaves like a button. + // Let screen readers know that: + + // this anchor behaves like a button state.videoVolumeControl.buttonEl.attr('role', gettext('button')); + + // what its name is: (title attribute is set in video.html template): + state.videoVolumeControl.buttonEl.attr('aria-label', gettext('Volume')); + + // what its state is: + state.videoVolumeControl.buttonEl.attr('aria-disabled', 'false'); } /** diff --git a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js index 4b9f532e43..0ec9a5a96f 100644 --- a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js @@ -79,9 +79,18 @@ function () { }); state.videoSpeedControl.setSpeed(state.speed); + // ARIA - // Let screen readers know this anchor behaves like a button. + // Let screen readers know that: + + // this anchor behaves like a button state.videoSpeedControl.el.children('a').attr('role', gettext('button')); + + // what its name is: (title attribute is set in video.html template): + state.videoSpeedControl.el.children('a').attr('aria-label', 'Speeds'); + + // what its state is: + state.videoSpeedControl.el.children('a').attr('aria-disabled', 'false'); } /** diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 57bb2a38d1..d606245420 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -105,9 +105,18 @@ function () { this.videoCaption.hideCaptions(true); this.videoCaption.hideSubtitlesEl.hide(); } + // ARIA - // Let screen readers know this anchor behaves like a button. - this.videoCaption.hideSubtitlesEl.attr('role', gettext('button')); + // Let screen readers know that: + + // this anchor behaves like a button + this.videoCaption.hideSubtitlesEl.attr('role', gettext('button')); + // what its name is: + // what its name is: (title attribute is set in video.html template): + // Speeds + + // what its state is: + this.videoCaption.hideSubtitlesEl.attr('aria-disabled', 'false'); } // function bindHandlers() diff --git a/lms/templates/video.html b/lms/templates/video.html index 3d0b9bd936..1974a08e6e 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -66,7 +66,7 @@ ${_('Fill browser')} ${_('HD')} - ${_('Captions')} + ${_('Turn off captions')} From a9b2f2dea0e80d48592e362a22250b45730c9b64 Mon Sep 17 00:00:00 2001 From: jmclaus Date: Sun, 29 Sep 2013 01:50:34 +0200 Subject: [PATCH 03/10] Volume slider announces its name and value to screen readers --- .../xmodule/xmodule/css/video/display.scss | 1 + .../xmodule/js/src/video/04_video_control.js | 3 ++ .../js/src/video/07_video_volume_control.js | 50 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index c087d18098..a0b514ab0f 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -109,6 +109,7 @@ div.video { -webkit-transition: -webkit-transform 0.7s ease-in-out; -moz-transition: -moz-transform 0.7s ease-in-out; -ms-transition: -ms-transform 0.7s ease-in-out; + tabindex: -1; transition: transform 0.7s ease-in-out; @include transform(scaleY(0.5) translate3d(0, 50%, 0)); diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js index f31ed1caee..5272a3fb64 100644 --- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js @@ -70,10 +70,13 @@ function () { // these anchors behaves like buttons state.videoControl.playPauseEl.attr('role', gettext('button')); state.videoControl.fullScreenEl.attr('role', gettext('button')); + // and this one as a slider + state.videoControl.sliderEl.find('.ui-slider-handle').attr('role', gettext('slider')); // what their names are: (title attribute are set in video.html template): // Play, Pause // Fill browser + state.videoControl.sliderEl.find('.ui-slider-handle').attr('title', gettext('video slider')); // what their states are: state.videoControl.playPauseEl.attr('aria-disabled', 'false'); diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index 9eda8eade7..d7116f24dc 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -74,6 +74,18 @@ function () { // what its state is: state.videoVolumeControl.buttonEl.attr('aria-disabled', 'false'); + + // Volume slider + state.videoVolumeControl.volumeSliderHandleEl = state.videoVolumeControl.volumeSliderEl.find('.ui-slider-handle'); + state.videoVolumeControl.volumeSliderHandleEl.attr({ + 'role': gettext('slider'), + 'title': 'volume', + 'aria-disabled': 'false', + 'aria-valuetext': getVolumeDescription(state.videoVolumeControl.slider.slider('option', 'value')), + 'aria-valuenow': state.videoVolumeControl.slider.slider('option', 'value'), + 'aria-valuemin': state.videoVolumeControl.slider.slider('option', 'min'), + 'aria-valuemax': state.videoVolumeControl.slider.slider('option', 'max') + }); } /** @@ -159,6 +171,9 @@ function () { }); this.trigger('videoPlayer.onVolumeChange', ui.value); + // ARIA + this.videoVolumeControl.volumeSliderHandleEl.attr('aria-valuenow', ui.value); + this.videoVolumeControl.volumeSliderHandleEl.attr('aria-valuetext', getVolumeDescription(ui.value)); } function toggleMute(event) { @@ -167,8 +182,43 @@ function () { if (this.videoVolumeControl.currentVolume > 0) { this.videoVolumeControl.previousVolume = this.videoVolumeControl.currentVolume; this.videoVolumeControl.slider.slider('option', 'value', 0); + // ARIA + state.videoVolumeControl.volumeSliderHandleEl.attr({ + 'aria-valuetext': getVolumeDescription(0), + 'aria-valuenow': 0 + }); } else { this.videoVolumeControl.slider.slider('option', 'value', this.videoVolumeControl.previousVolume); + // ARIA + state.videoVolumeControl.volumeSliderHandleEl.attr({ + 'aria-valuetext': getVolumeDescription(this.videoVolumeControl.previousVolume), + 'aria-valuenow': this.videoVolumeControl.previousVolume + }); + } + } + + // ARIA + function getVolumeDescription(vol) { + if (vol === 0) { + return 'silent'; + } + else if (vol <= 20) { + return 'very low'; + } + else if (vol <= 40) { + return 'low'; + } + else if (vol <= 60) { + return 'average'; + } + else if (vol <= 80) { + return 'loud'; + } + else if (vol <= 99) { + return 'very loud'; + } + else { + return 'maximum'; } } From e5895adb2b7e11de8b4ae98b4a9175eb4d7b1a3c Mon Sep 17 00:00:00 2001 From: jmclaus Date: Mon, 30 Sep 2013 14:16:26 +0200 Subject: [PATCH 04/10] Progress slider announces its nabe and value to screen readers --- .../js/src/video/06_video_progress_slider.js | 68 +++++++++++++++++++ lms/templates/video.html | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js index b45494ca34..7741f6eadb 100644 --- a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js +++ b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js @@ -54,6 +54,19 @@ function () { function _buildHandle(state) { state.videoProgressSlider.handle = state.videoProgressSlider.el.find('.ui-slider-handle'); + + // ARIA + // Let screen readers know that this anchor behaves like a slider, is + // named 'video position' and give its state + state.videoProgressSlider.handle.attr({ + 'role': gettext('slider'), + 'title': 'video position', + 'aria-disabled': 'false', + 'aria-valuetext': getTimeDescription(state.videoProgressSlider.slider.slider('option', 'value')) + //'aria-valuenow': state.videoProgressSlider.slider.slider('option', 'value'), + //'aria-valuemin': state.videoProgressSlider.slider.slider('option', 'min'), + //'aria-valuemax': state.videoProgressSlider.slider.slider('option', 'max') + }); } // *************************************************************** @@ -74,6 +87,10 @@ function () { this.videoProgressSlider.frozen = true; this.trigger('videoPlayer.onSlideSeek', {'type': 'onSlideSeek', 'time': ui.value}); + + // ARIA + this.videoProgressSlider.handle.attr('aria-valuetext', + getTimeDescription(this.videoPlayer.currentTime)); } function onStop(event, ui) { @@ -83,6 +100,10 @@ function () { this.trigger('videoPlayer.onSlideSeek', {'type': 'onSlideSeek', 'time': ui.value}); + // ARIA + this.videoProgressSlider.handle.attr('aria-valuetext', + getTimeDescription(this.videoPlayer.currentTime)); + setTimeout(function() { _this.videoProgressSlider.frozen = false; }, 200); @@ -99,6 +120,53 @@ function () { } } + function getTimeDescription(time) { + var seconds = Math.floor(time), + minutes = Math.floor(seconds / 60), + hours = Math.floor(minutes / 60), + hrStr, minStr, secStr; + seconds = seconds % 60; + minutes = minutes % 60; + + hrStr = hours.toString(10); + minStr = minutes.toString(10); + secStr = seconds.toString(10); + + if (hours) { + hrStr += (hours < 2 ? ' hour ' : ' hours '); + if (minutes) { + minStr += (minutes < 2 ? ' minute ' : ' minutes '); + } + else { + minStr += ' 0 minutes '; + } + if (seconds) { + secStr += (seconds < 2 ? ' second ' : ' seconds '); + } + else { + secStr += ' 0 seconds '; + } + return hrStr + minStr + secStr; + } + else if (minutes) { + minStr += (minutes < 2 ? ' minute ' : ' minutes '); + if (seconds) { + secStr += (seconds < 2 ? ' second ' : ' seconds '); + } + else { + secStr += ' 0 seconds '; + } + return minStr + secStr; + } + else if (seconds) { + secStr += (seconds < 2 ? ' second ' : ' seconds '); + return secStr; + } + else { + return '0 seconds'; + } + } + }); }(RequireJS.requirejs, RequireJS.require, RequireJS.define)); diff --git a/lms/templates/video.html b/lms/templates/video.html index 1974a08e6e..8ddaa68049 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -42,7 +42,7 @@
-
+
From 9d418755a681c6e520d36a23b0bade63f9e921b1 Mon Sep 17 00:00:00 2001 From: jmclaus Date: Wed, 2 Oct 2013 16:54:48 +0200 Subject: [PATCH 10/10] Added changes made to video.html to fixtures. Addressed latest PR comments --- .../xmodule/xmodule/js/fixtures/video.html | 12 +++++------ .../xmodule/js/fixtures/video_all.html | 12 +++++------ .../js/fixtures/video_yt_multiple.html | 12 +++++------ .../js/src/video/06_video_progress_slider.js | 20 +++++++------------ .../js/src/video/07_video_volume_control.js | 20 +++++++------------ lms/templates/video.html | 4 ++-- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html index 410b5869f0..f607430ba0 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video.html @@ -26,26 +26,26 @@
    -
  • +
  • 0:00 / 0:00
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_all.html b/common/lib/xmodule/xmodule/js/fixtures/video_all.html index f155905282..57052bf65d 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_all.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_all.html @@ -29,26 +29,26 @@
    -
  • +
  • 0:00 / 0:00
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html index 834de10406..c6b40cdf16 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html +++ b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html @@ -26,26 +26,26 @@
    -
  • +
  • 0:00 / 0:00
diff --git a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js index 5750a7574e..18fa7ee3ad 100644 --- a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js +++ b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js @@ -138,35 +138,29 @@ function () { hrStr += (hours < 2 ? ' hour ' : ' hours '); if (minutes) { minStr += (minutes < 2 ? ' minute ' : ' minutes '); - } - else { + } else { minStr += ' 0 minutes '; } if (seconds) { secStr += (seconds < 2 ? ' second ' : ' seconds '); - } - else { + } else { secStr += ' 0 seconds '; } return hrStr + minStr + secStr; - } - else if (minutes) { + } else if (minutes) { minStr += (minutes < 2 ? ' minute ' : ' minutes '); if (seconds) { secStr += (seconds < 2 ? ' second ' : ' seconds '); - } - else { + } else { secStr += ' 0 seconds '; } return minStr + secStr; - } - else if (seconds) { + } else if (seconds) { secStr += (seconds < 2 ? ' second ' : ' seconds '); return secStr; } - else { - return '0 seconds'; - } + + return '0 seconds'; } }); diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index a9c7aef269..ea813f3912 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -216,25 +216,19 @@ function () { function getVolumeDescription(vol) { if (vol === 0) { return 'muted'; - } - else if (vol <= 20) { + } else if (vol <= 20) { return 'very low'; - } - else if (vol <= 40) { + } else if (vol <= 40) { return 'low'; - } - else if (vol <= 60) { + } else if (vol <= 60) { return 'average'; - } - else if (vol <= 80) { + } else if (vol <= 80) { return 'loud'; - } - else if (vol <= 99) { + } else if (vol <= 99) { return 'very loud'; } - else { - return 'maximum'; - } + + return 'maximum'; } }); diff --git a/lms/templates/video.html b/lms/templates/video.html index 89d376090c..caf0aaa06f 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -51,14 +51,14 @@