diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 78237150cd..bd28c47a74 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -316,3 +316,6 @@ Common: Updated CodeJail. Common: Allow setting of authentication session cookie name. LMS: Option to email students when enroll/un-enroll them. + +Blades: Added WAI-ARIA markup to the video player controls. These are now fully +accessible by screen readers. diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index a0b514ab0f..c087d18098 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -109,7 +109,6 @@ 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 db917dc258..796ba07060 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 @@ -65,23 +65,10 @@ function () { } // ARIA - // Let screen readers know that: - - // These anchors behaves like buttons, named 'Play' or 'Pause', and - // 'Fill screen' (the title attribute is set in video.html template). - state.videoControl.playPauseEl.attr({ - 'role': gettext('button'), - 'aria-disabled': false - }); - - state.videoControl.fullScreenEl.attr({ - 'role': gettext('button'), - 'aria-disabled': false - }); - - // This anchor behaves as a slider named 'video slider'. + // Let screen readers know that this anchor, representing the slider + // handle, behaves as a slider named 'video slider'. state.videoControl.sliderEl.find('.ui-slider-handle').attr({ - 'role': gettext('slider'), + 'role': 'slider', 'title': gettext('video slider') }); } @@ -189,14 +176,14 @@ function () { this.videoControl.fullScreenState = false; fullScreenClassNameEl.removeClass('video-fullscreen'); this.isFullScreen = false; - this.videoControl.fullScreenEl.attr('title', gettext('Fill browser')); - this.videoControl.fullScreenEl.text(gettext('Fill browser')); + this.videoControl.fullScreenEl.attr('title', gettext('Fill browser')) + .text(gettext('Fill browser')); } else { this.videoControl.fullScreenState = true; fullScreenClassNameEl.addClass('video-fullscreen'); this.isFullScreen = true; - this.videoControl.fullScreenEl.attr('title', gettext('Exit full browser')); - this.videoControl.fullScreenEl.text(gettext('Exit full browser')); + this.videoControl.fullScreenEl.attr('title', gettext('Exit full browser')) + .text(gettext('Exit full browser')); } this.trigger('videoCaption.resize', null); 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 70f00e3e18..cff103468d 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,16 +43,6 @@ function () { state.videoQualityControl.el.show(); state.videoQualityControl.quality = null; - - // ARIA - // Let screen readers know that: - - // This anchor behaves as a button named 'HD'. - // (the title attribute is set in video.html template). - state.videoQualityControl.el.attr({ - 'role': gettext('button'), - 'aria-disabled': false - }); } // function _bindHandlers(state) 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 728b108ef4..5750a7574e 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 @@ -56,11 +56,12 @@ function () { state.videoProgressSlider.handle = state.videoProgressSlider.el.find('.ui-slider-handle'); // ARIA - // Let screen readers know that: - - // This anchor behaves as a button named 'video position'. + // We just want the knob to be selectable with keyboard + state.videoProgressSlider.el.attr('tabindex', -1); + // Let screen readers know that this anchor, representing the slider + // handle, behaves as a slider named 'video position'. state.videoProgressSlider.handle.attr({ - 'role': gettext('slider'), + 'role': 'slider', 'title': 'video position', 'aria-disabled': false, 'aria-valuetext': getTimeDescription(state.videoProgressSlider.slider.slider('option', 'value')) @@ -126,46 +127,46 @@ function () { minutes = Math.floor(seconds / 60), hours = Math.floor(minutes / 60), hrStr, minStr, secStr; - seconds = seconds % 60; - minutes = minutes % 60; + seconds = seconds % 60; + minutes = minutes % 60; - hrStr = hours.toString(10); - minStr = minutes.toString(10); - secStr = seconds.toString(10); + hrStr = hours.toString(10); + minStr = minutes.toString(10); + secStr = seconds.toString(10); - if (hours) { - hrStr += (hours < 2 ? ' hour ' : ' hours '); - if (minutes) { + 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 { - minStr += ' 0 minutes '; - } - if (seconds) { + else if (seconds) { secStr += (seconds < 2 ? ' second ' : ' seconds '); + return secStr; } else { - secStr += ' 0 seconds '; - } - return hrStr + minStr + secStr; - } - else if (minutes) { - minStr += (minutes < 2 ? ' minute ' : ' minutes '); - if (seconds) { - secStr += (seconds < 2 ? ' second ' : ' seconds '); + return '0 seconds'; } - else { - secStr += ' 0 seconds '; - } - return minStr + secStr; - } - else if (seconds) { - secStr += (seconds < 2 ? ' second ' : ' seconds '); - return secStr; - } - else { - 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 b9f5fa28c0..a9c7aef269 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 @@ -67,25 +67,29 @@ function () { // Let screen readers know that: // This anchor behaves as a button named 'Volume'. - // (the title attribute is set in video.html template). - var buttonStr = state.videoVolumeControl.currentVolume === 0 ? gettext('Volume muted') : gettext('Volume'); - state.videoVolumeControl.buttonEl.attr({ - 'role': gettext('button'), - 'aria-label': buttonStr, // Doesn't read the title attribute, why? - 'aria-disabled': false - }); + var buttonStr = gettext( + state.videoVolumeControl.currentVolume === 0 + ? 'Volume muted' + : 'Volume' + ); + // We add the aria-label attribute because the title attribute cannot be + // read. + state.videoVolumeControl.buttonEl.attr('aria-label', buttonStr); - // The anchor representing the slider handle behaves as a slider named - // volume. - state.videoVolumeControl.volumeSliderHandleEl = state.videoVolumeControl.volumeSliderEl.find('.ui-slider-handle'); + // Let screen readers know that this anchor, representing the slider + // handle, behaves as a slider named 'volume'. + var volumeSlider = state.videoVolumeControl.slider; + state.videoVolumeControl.volumeSliderHandleEl = state.videoVolumeControl + .volumeSliderEl + .find('.ui-slider-handle'); state.videoVolumeControl.volumeSliderHandleEl.attr({ - 'role': gettext('slider'), + 'role': 'slider', 'title': 'volume', 'aria-disabled': false, - 'aria-valuemin': state.videoVolumeControl.slider.slider('option', 'min'), - 'aria-valuemax': state.videoVolumeControl.slider.slider('option', 'max'), - 'aria-valuenow': state.videoVolumeControl.slider.slider('option', 'value'), - 'aria-valuetext': getVolumeDescription(state.videoVolumeControl.slider.slider('option', 'value')) + 'aria-valuemin': volumeSlider.slider('option', 'min'), + 'aria-valuemax': volumeSlider.slider('option', 'max'), + 'aria-valuenow': volumeSlider.slider('option', 'value'), + 'aria-valuetext': getVolumeDescription(volumeSlider.slider('option', 'value')) }); } @@ -180,7 +184,9 @@ function () { }); this.videoVolumeControl.buttonEl.attr( - 'aria-label', this.videoVolumeControl.currentVolume === 0 ? gettext('Volume muted') : gettext('Volume') + 'aria-label', this.videoVolumeControl.currentVolume === 0 + ? gettext('Volume muted') + : gettext('Volume') ); } 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 1f867afac1..67f62edf95 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,16 +79,6 @@ function () { }); state.videoSpeedControl.setSpeed(state.speed); - - // ARIA - // Let screen readers know that: - - // This anchor behaves as a button named 'Speeds'. - // (the title attribute is set in video.html template). - state.videoSpeedControl.el.children('a').attr({ - 'role': gettext('button'), - '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 ea0d5caaed..b97717e97c 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,16 +105,6 @@ function () { this.videoCaption.hideCaptions(true); this.videoCaption.hideSubtitlesEl.hide(); } - - // ARIA - // Let screen readers know that: - - // This anchor behaves as a button named 'CC'. - // (the title attribute is set in video.html template). - this.videoCaption.hideSubtitlesEl.attr({ - 'role': gettext('button'), - 'aria-disabled': 'false' - }); } // function bindHandlers() diff --git a/lms/templates/video.html b/lms/templates/video.html index 8ddaa68049..89d376090c 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -46,27 +46,27 @@