Merge pull request #3678 from edx/anton/refactor-volume-control
Video: refactor volume control.
This commit is contained in:
@@ -458,14 +458,14 @@ div.video {
|
||||
float: left;
|
||||
position: relative;
|
||||
|
||||
&.open {
|
||||
&.is-opened {
|
||||
.volume-slider-container {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.muted {
|
||||
&.is-muted {
|
||||
& > a {
|
||||
background-image: url('../images/mute.png');
|
||||
}
|
||||
|
||||
@@ -141,8 +141,7 @@ function (VideoPlayer) {
|
||||
|
||||
state.videoEl = $('video, iframe');
|
||||
|
||||
expect(state.videoVolumeControl).toBeUndefined();
|
||||
expect(state.el.find('div.volume')).not.toExist();
|
||||
expect(state.el.find('.volume')).not.toExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -450,42 +449,34 @@ function (VideoPlayer) {
|
||||
}, 'currentTime got updated', 10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the video is not playing', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
describe('when the video is not playing', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
|
||||
spyOn(state.videoPlayer, 'updatePlayTime').andCallThrough();
|
||||
spyOn(state, 'setSpeed').andCallThrough();
|
||||
spyOn(state.videoPlayer, 'log').andCallThrough();
|
||||
spyOn(state.videoPlayer.player, 'setPlaybackRate').andCallThrough();
|
||||
spyOn(state.videoPlayer, 'setPlaybackRate').andCallThrough();
|
||||
});
|
||||
spyOn(state.videoPlayer, 'updatePlayTime').andCallThrough();
|
||||
spyOn(state, 'setSpeed').andCallThrough();
|
||||
spyOn(state.videoPlayer, 'log').andCallThrough();
|
||||
spyOn(state.videoPlayer.player, 'setPlaybackRate').andCallThrough();
|
||||
spyOn(state.videoPlayer, 'setPlaybackRate').andCallThrough();
|
||||
});
|
||||
|
||||
it('video has a correct speed', function () {
|
||||
state.speed = '2.0';
|
||||
state.videoPlayer.onPlay();
|
||||
expect(state.videoPlayer.setPlaybackRate)
|
||||
.toHaveBeenCalledWith('2.0');
|
||||
state.videoPlayer.onPlay();
|
||||
expect(state.videoPlayer.setPlaybackRate.calls.length)
|
||||
.toEqual(1);
|
||||
});
|
||||
|
||||
it('video has a correct volume', function () {
|
||||
spyOn(state.videoPlayer.player, 'setVolume');
|
||||
state.currentVolume = '0.26';
|
||||
state.videoPlayer.onPlay();
|
||||
expect(state.videoPlayer.player.setVolume)
|
||||
.toHaveBeenCalledWith('0.26');
|
||||
it('video has a correct speed', function () {
|
||||
state.speed = '2.0';
|
||||
state.videoPlayer.onPlay();
|
||||
expect(state.videoPlayer.setPlaybackRate)
|
||||
.toHaveBeenCalledWith('2.0');
|
||||
state.videoPlayer.onPlay();
|
||||
expect(state.videoPlayer.setPlaybackRate.calls.length)
|
||||
.toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onVolumeChange', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
|
||||
state.videoPlayer.onReady();
|
||||
state.videoEl = $('video, iframe');
|
||||
});
|
||||
|
||||
@@ -502,10 +493,10 @@ function (VideoPlayer) {
|
||||
|
||||
it('video has a correct volume', function () {
|
||||
spyOn(state.videoPlayer.player, 'setVolume');
|
||||
state.currentVolume = '0.26';
|
||||
state.videoPlayer.onPlay();
|
||||
state.videoVolumeControl.volume = 26;
|
||||
state.el.trigger('play');
|
||||
expect(state.videoPlayer.player.setVolume)
|
||||
.toHaveBeenCalledWith('0.26');
|
||||
.toHaveBeenCalledWith(26);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,209 +1,313 @@
|
||||
(function (undefined) {
|
||||
describe('VideoVolumeControl', function () {
|
||||
var state, oldOTBD;
|
||||
(function () {
|
||||
'use strict';
|
||||
describe('VideoVolumeControl', function () {
|
||||
var state, oldOTBD, volumeControl;
|
||||
|
||||
beforeEach(function () {
|
||||
oldOTBD = window.onTouchBasedDevice;
|
||||
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
|
||||
.andReturn(null);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
$('source').remove();
|
||||
window.onTouchBasedDevice = oldOTBD;
|
||||
state.storage.clear();
|
||||
});
|
||||
|
||||
it('Volume level has correct value even if cookie is broken', function () {
|
||||
$.cookie.andReturn('broken_cookie');
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
expect(volumeControl.volume).toEqual(100);
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
beforeEach(function () {
|
||||
oldOTBD = window.onTouchBasedDevice;
|
||||
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
|
||||
.andReturn(null);
|
||||
spyOn($.fn, 'slider').andCallThrough();
|
||||
$.cookie.andReturn('75');
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
$('source').remove();
|
||||
window.onTouchBasedDevice = oldOTBD;
|
||||
state.storage.clear();
|
||||
it('initialize volume to 75%', function () {
|
||||
expect(volumeControl.volume).toEqual(75);
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
it('render the volume control', function () {
|
||||
expect(state.videoControl.secondaryControlsEl.html())
|
||||
.toContain('<div class="volume">\n');
|
||||
});
|
||||
|
||||
it('create the slider', function () {
|
||||
expect($.fn.slider.calls[2].args).toEqual([{
|
||||
orientation: 'vertical',
|
||||
range: 'min',
|
||||
min: 0,
|
||||
max: 100,
|
||||
slide: jasmine.any(Function)
|
||||
}]);
|
||||
expect($.fn.slider).toHaveBeenCalledWith(
|
||||
'value', volumeControl.volume
|
||||
);
|
||||
});
|
||||
|
||||
it('add ARIA attributes to live region', function () {
|
||||
var liveRegion = $('.video-live-region');
|
||||
|
||||
expect(liveRegion).toHaveAttrs({
|
||||
'role': 'status',
|
||||
'aria-live': 'polite',
|
||||
'aria-atomic': 'false'
|
||||
});
|
||||
});
|
||||
|
||||
it('add ARIA attributes to volume control', function () {
|
||||
var button = $('.volume > a');
|
||||
|
||||
expect(button).toHaveAttrs({
|
||||
'role': 'button',
|
||||
'title': 'Volume',
|
||||
'aria-disabled': 'false'
|
||||
});
|
||||
});
|
||||
|
||||
it('bind the volume control', function () {
|
||||
var button = $('.volume > a');
|
||||
|
||||
expect(button).toHandle('keydown');
|
||||
expect(button).toHandle('mousedown');
|
||||
expect($('.volume')).not.toHaveClass('is-opened');
|
||||
|
||||
$('.volume').mouseenter();
|
||||
expect($('.volume')).toHaveClass('is-opened');
|
||||
|
||||
$('.volume').mouseleave();
|
||||
expect($('.volume')).not.toHaveClass('is-opened');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVolume', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
|
||||
this.addMatchers({
|
||||
assertLiveRegionState: function (volume, expectation) {
|
||||
var region = $('.video-live-region');
|
||||
|
||||
var getExpectedText = function (text) {
|
||||
return text + ' Volume.';
|
||||
};
|
||||
|
||||
this.actual.setVolume(volume, true, true);
|
||||
return region.text() === getExpectedText(expectation);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('update is not called, if new volume equals current', function () {
|
||||
volumeControl.volume = 60;
|
||||
spyOn(volumeControl, 'updateSliderView');
|
||||
volumeControl.setVolume(60, false, true);
|
||||
expect(volumeControl.updateSliderView).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('volume is changed on sliding', function () {
|
||||
volumeControl.onSlideHandler(null, {value: 99});
|
||||
expect(volumeControl.volume).toBe(99);
|
||||
});
|
||||
|
||||
describe('when the new volume is more than 0', function () {
|
||||
beforeEach(function () {
|
||||
spyOn($.fn, 'slider').andCallThrough();
|
||||
$.cookie.andReturn('75');
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl.setVolume(60, false, true);
|
||||
});
|
||||
|
||||
it('initialize currentVolume to 75%', function () {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(75);
|
||||
it('set the player volume', function () {
|
||||
expect(volumeControl.volume).toEqual(60);
|
||||
});
|
||||
|
||||
it('render the volume control', function () {
|
||||
expect(state.videoControl.secondaryControlsEl.html())
|
||||
.toContain("<div class=\"volume\">\n");
|
||||
});
|
||||
|
||||
it('create the slider', function () {
|
||||
expect($.fn.slider).toHaveBeenCalledWith({
|
||||
orientation: "vertical",
|
||||
range: "min",
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: state.videoVolumeControl.currentVolume,
|
||||
change: state.videoVolumeControl.onChange,
|
||||
slide: state.videoVolumeControl.onChange
|
||||
});
|
||||
});
|
||||
|
||||
it('add ARIA attributes to slider handle', function () {
|
||||
var sliderHandle = $('div.volume-slider>a.ui-slider-handle'),
|
||||
arr = [
|
||||
'Muted', 'Very low', 'Low', 'Average', 'Loud',
|
||||
'Very loud', 'Maximum'
|
||||
];
|
||||
|
||||
expect(sliderHandle).toHaveAttrs({
|
||||
'role': 'slider',
|
||||
'title': 'Volume',
|
||||
'aria-disabled': 'false',
|
||||
'aria-valuemin': '0',
|
||||
'aria-valuemax': '100'
|
||||
});
|
||||
expect(sliderHandle.attr('aria-valuenow')).toBeInRange(0, 100);
|
||||
expect(sliderHandle.attr('aria-valuetext')).toBeInArray(arr);
|
||||
});
|
||||
|
||||
it('add ARIA attributes to volume control', function () {
|
||||
var volumeControl = $('div.volume>a');
|
||||
|
||||
expect(volumeControl).toHaveAttrs({
|
||||
'role': 'button',
|
||||
'title': 'Volume',
|
||||
'aria-disabled': 'false'
|
||||
});
|
||||
});
|
||||
|
||||
it('bind the volume control', function () {
|
||||
expect($('.volume>a')).toHandleWith(
|
||||
'click', state.videoVolumeControl.toggleMute
|
||||
);
|
||||
expect($('.volume')).not.toHaveClass('open');
|
||||
|
||||
$('.volume').mouseenter();
|
||||
expect($('.volume')).toHaveClass('open');
|
||||
|
||||
$('.volume').mouseleave();
|
||||
expect($('.volume')).not.toHaveClass('open');
|
||||
it('remove muted class', function () {
|
||||
expect($('.volume')).not.toHaveClass('is-muted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChange', function () {
|
||||
var initialData = [{
|
||||
range: 'Muted',
|
||||
value: 0,
|
||||
expectation: 'Muted'
|
||||
}, {
|
||||
range: 'in ]0,20]',
|
||||
value: 10,
|
||||
expectation: 'Very low'
|
||||
}, {
|
||||
range: 'in ]20,40]',
|
||||
value: 30,
|
||||
expectation: 'Low'
|
||||
}, {
|
||||
range: 'in ]40,60]',
|
||||
value: 50,
|
||||
expectation: 'Average'
|
||||
}, {
|
||||
range: 'in ]60,80]',
|
||||
value: 70,
|
||||
expectation: 'Loud'
|
||||
}, {
|
||||
range: 'in ]80,100[',
|
||||
value: 90,
|
||||
expectation: 'Very loud'
|
||||
}, {
|
||||
range: 'Maximum',
|
||||
value: 100,
|
||||
expectation: 'Maximum'
|
||||
}];
|
||||
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
});
|
||||
|
||||
describe('when the new volume is more than 0', function () {
|
||||
beforeEach(function () {
|
||||
state.videoVolumeControl.onChange(void 0, {
|
||||
value: 60
|
||||
});
|
||||
});
|
||||
|
||||
it('set the player volume', function () {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(60);
|
||||
});
|
||||
|
||||
it('remote muted class', function () {
|
||||
expect($('.volume')).not.toHaveClass('muted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the new volume is 0', function () {
|
||||
beforeEach(function () {
|
||||
state.videoVolumeControl.onChange(void 0, {
|
||||
value: 0
|
||||
});
|
||||
});
|
||||
|
||||
it('set the player volume', function () {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(0);
|
||||
});
|
||||
|
||||
it('add muted class', function () {
|
||||
expect($('.volume')).toHaveClass('muted');
|
||||
});
|
||||
});
|
||||
|
||||
$.each(initialData, function (index, data) {
|
||||
describe('when the new volume is ' + data.range, function () {
|
||||
beforeEach(function () {
|
||||
state.videoVolumeControl.onChange(void 0, {
|
||||
value: data.value
|
||||
});
|
||||
});
|
||||
|
||||
it('changes ARIA attributes', function () {
|
||||
var sliderHandle = $(
|
||||
'div.volume-slider>a.ui-slider-handle'
|
||||
);
|
||||
|
||||
expect(sliderHandle).toHaveAttrs({
|
||||
'aria-valuenow': data.value.toString(10),
|
||||
'aria-valuetext': data.expectation
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when the new volume is more than 0, but was 0', function () {
|
||||
it('remove muted class', function () {
|
||||
volumeControl.setVolume(0, false, true);
|
||||
expect($('.volume')).toHaveClass('is-muted');
|
||||
state.el.trigger('volumechange', [20]);
|
||||
expect($('.volume')).not.toHaveClass('is-muted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleMute', function () {
|
||||
describe('when the new volume is 0', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl.setVolume(0, false, true);
|
||||
});
|
||||
|
||||
describe('when the current volume is more than 0', function () {
|
||||
beforeEach(function () {
|
||||
state.videoVolumeControl.currentVolume = 60;
|
||||
state.videoVolumeControl.buttonEl.trigger('click');
|
||||
});
|
||||
|
||||
it('save the previous volume', function () {
|
||||
expect(state.videoVolumeControl.previousVolume).toEqual(60);
|
||||
});
|
||||
|
||||
it('set the player volume', function () {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(0);
|
||||
});
|
||||
it('set the player volume', function () {
|
||||
expect(volumeControl.volume).toEqual(0);
|
||||
});
|
||||
|
||||
describe('when the current volume is 0', function () {
|
||||
beforeEach(function () {
|
||||
state.videoVolumeControl.currentVolume = 0;
|
||||
state.videoVolumeControl.previousVolume = 60;
|
||||
state.videoVolumeControl.buttonEl.trigger('click');
|
||||
});
|
||||
it('add muted class', function () {
|
||||
expect($('.volume')).toHaveClass('is-muted');
|
||||
});
|
||||
});
|
||||
|
||||
it('set the player volume to previous volume', function () {
|
||||
expect(state.videoVolumeControl.currentVolume).toEqual(60);
|
||||
});
|
||||
it('when the new volume is Muted', function () {
|
||||
expect(volumeControl).assertLiveRegionState(0, 'Muted');
|
||||
});
|
||||
|
||||
it('when the new volume is in ]0,20]', function () {
|
||||
expect(volumeControl).assertLiveRegionState(10, 'Very low');
|
||||
});
|
||||
|
||||
it('when the new volume is in ]20,40]', function () {
|
||||
expect(volumeControl).assertLiveRegionState(30, 'Low');
|
||||
});
|
||||
|
||||
it('when the new volume is in ]40,60]', function () {
|
||||
expect(volumeControl).assertLiveRegionState(50, 'Average');
|
||||
});
|
||||
|
||||
it('when the new volume is in ]60,80]', function () {
|
||||
expect(volumeControl).assertLiveRegionState(70, 'Loud');
|
||||
});
|
||||
|
||||
it('when the new volume is in ]80,100[', function () {
|
||||
expect(volumeControl).assertLiveRegionState(90, 'Very loud');
|
||||
});
|
||||
|
||||
it('when the new volume is Maximum', function () {
|
||||
expect(volumeControl).assertLiveRegionState(100, 'Maximum');
|
||||
});
|
||||
});
|
||||
|
||||
describe('increaseVolume', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
it('volume is increased correctly', function () {
|
||||
volumeControl.volume = 60;
|
||||
state.el.trigger(jQuery.Event("keydown", {
|
||||
keyCode: $.ui.keyCode.UP
|
||||
}));
|
||||
expect(volumeControl.volume).toEqual(80);
|
||||
});
|
||||
|
||||
it('volume level is not changed if it is already max', function () {
|
||||
volumeControl.volume = 100;
|
||||
volumeControl.increaseVolume();
|
||||
expect(volumeControl.volume).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decreaseVolume', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
it('volume is decreased correctly', function () {
|
||||
volumeControl.volume = 60;
|
||||
state.el.trigger(jQuery.Event("keydown", {
|
||||
keyCode: $.ui.keyCode.DOWN
|
||||
}));
|
||||
expect(volumeControl.volume).toEqual(40);
|
||||
});
|
||||
|
||||
it('volume level is not changed if it is already min', function () {
|
||||
volumeControl.volume = 0;
|
||||
volumeControl.decreaseVolume();
|
||||
expect(volumeControl.volume).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleMute', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
describe('when the current volume is more than 0', function () {
|
||||
beforeEach(function () {
|
||||
volumeControl.volume = 60;
|
||||
volumeControl.button.trigger('mousedown');
|
||||
});
|
||||
|
||||
it('save the previous volume', function () {
|
||||
expect(volumeControl.storedVolume).toEqual(60);
|
||||
});
|
||||
|
||||
it('set the player volume', function () {
|
||||
expect(volumeControl.volume).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the current volume is 0', function () {
|
||||
beforeEach(function () {
|
||||
volumeControl.volume = 0;
|
||||
volumeControl.storedVolume = 60;
|
||||
volumeControl.button.trigger('mousedown');
|
||||
});
|
||||
|
||||
it('set the player volume to previous volume', function () {
|
||||
expect(volumeControl.volume).toEqual(60);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyDownHandler', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
var assertVolumeIsNotChanged = function (eventObject) {
|
||||
volumeControl.volume = 60;
|
||||
state.el.trigger(jQuery.Event("keydown", eventObject));
|
||||
expect(volumeControl.volume).toEqual(60);
|
||||
};
|
||||
|
||||
it('nothing happens if ALT+keyUp are pushed down', function () {
|
||||
assertVolumeIsNotChanged({
|
||||
keyCode: $.ui.keyCode.UP,
|
||||
altKey: true
|
||||
});
|
||||
});
|
||||
|
||||
it('nothing happens if SHIFT+keyUp are pushed down', function () {
|
||||
assertVolumeIsNotChanged({
|
||||
keyCode: $.ui.keyCode.UP,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
|
||||
it('nothing happens if SHIFT+keyDown are pushed down', function () {
|
||||
assertVolumeIsNotChanged({
|
||||
keyCode: $.ui.keyCode.DOWN,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('keyDownButtonHandler', function () {
|
||||
beforeEach(function () {
|
||||
state = jasmine.initializePlayer();
|
||||
volumeControl = state.videoVolumeControl;
|
||||
});
|
||||
|
||||
it('nothing happens if ALT+ENTER are pushed down', function () {
|
||||
var isMuted = volumeControl.getMuteStatus();
|
||||
$('.volume > a').trigger(jQuery.Event("keydown", {
|
||||
keyCode: $.ui.keyCode.ENTER,
|
||||
altKey: true
|
||||
}));
|
||||
expect(volumeControl.getMuteStatus()).toEqual(isMuted);
|
||||
});
|
||||
})
|
||||
});
|
||||
}).call(this);
|
||||
|
||||
31
common/lib/xmodule/xmodule/js/src/video/00_i18n.js
Normal file
31
common/lib/xmodule/xmodule/js/src/video/00_i18n.js
Normal file
@@ -0,0 +1,31 @@
|
||||
(function (define) {
|
||||
'use strict';
|
||||
define(
|
||||
'video/00_i18n.js',
|
||||
[],
|
||||
function() {
|
||||
/**
|
||||
* i18n module.
|
||||
* @exports video/00_i18n.js
|
||||
* @return {object}
|
||||
*/
|
||||
|
||||
return {
|
||||
'Volume': gettext('Volume'),
|
||||
// Translators: Volume level equals 0%.
|
||||
'Muted': gettext('Muted'),
|
||||
// Translators: Volume level in range ]0,20]%
|
||||
'Very low': gettext('Very low'),
|
||||
// Translators: Volume level in range ]20,40]%
|
||||
'Low': gettext('Low'),
|
||||
// Translators: Volume level in range ]40,60]%
|
||||
'Average': gettext('Average'),
|
||||
// Translators: Volume level in range ]60,80]%
|
||||
'Loud': gettext('Loud'),
|
||||
// Translators: Volume level in range ]80,99]%
|
||||
'Very loud': gettext('Very loud'),
|
||||
// Translators: Volume level equals 100%.
|
||||
'Maximum': gettext('Maximum')
|
||||
};
|
||||
});
|
||||
}(RequireJS.define));
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
define(
|
||||
'video/01_initialize.js',
|
||||
['video/03_video_player.js', 'video/00_video_storage.js'],
|
||||
function (VideoPlayer, VideoStorage) {
|
||||
['video/03_video_player.js', 'video/00_video_storage.js', 'video/00_i18n.js'],
|
||||
function (VideoPlayer, VideoStorage, i18n) {
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
@@ -39,7 +39,7 @@ function (VideoPlayer, VideoStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_initializeModules(state)
|
||||
_initializeModules(state, i18n)
|
||||
.done(function () {
|
||||
// On iPad ready state occurs just after start playing.
|
||||
// We hide controls before video starts playing.
|
||||
@@ -341,11 +341,11 @@ function (VideoPlayer, VideoStorage) {
|
||||
state.captionHideTimeout = null;
|
||||
}
|
||||
|
||||
function _initializeModules(state) {
|
||||
function _initializeModules(state, i18n) {
|
||||
var dfd = $.Deferred(),
|
||||
modulesList = $.map(state.modules, function(module) {
|
||||
if ($.isFunction(module)) {
|
||||
return module(state);
|
||||
return module(state, i18n);
|
||||
} else if ($.isPlainObject(module)) {
|
||||
return module;
|
||||
}
|
||||
@@ -494,7 +494,6 @@ function (VideoPlayer, VideoStorage) {
|
||||
__dfd__: __dfd__,
|
||||
el: el,
|
||||
container: container,
|
||||
currentVolume: 100,
|
||||
id: id,
|
||||
isFullScreen: false,
|
||||
isTouch: isTouch,
|
||||
|
||||
@@ -98,7 +98,6 @@ function (HTML5Video, Resizer) {
|
||||
if (!state.isFlashMode() && state.speed != '1.0') {
|
||||
state.videoPlayer.setPlaybackRate(state.speed);
|
||||
}
|
||||
state.videoPlayer.player.setVolume(state.currentVolume);
|
||||
});
|
||||
|
||||
if (state.isYoutubeType()) {
|
||||
@@ -584,6 +583,10 @@ function (HTML5Video, Resizer) {
|
||||
_this.videoPlayer.onSpeedChange(speed);
|
||||
});
|
||||
|
||||
this.el.on('volumechange volumechange:silent', function (event, volume) {
|
||||
_this.videoPlayer.onVolumeChange(volume);
|
||||
});
|
||||
|
||||
this.videoPlayer.log('load_video');
|
||||
|
||||
availablePlaybackRates = this.videoPlayer.player
|
||||
@@ -919,7 +922,6 @@ function (HTML5Video, Resizer) {
|
||||
|
||||
function onVolumeChange(volume) {
|
||||
this.videoPlayer.player.setVolume(volume);
|
||||
this.el.trigger('volumechange', arguments);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -64,9 +64,9 @@ function () {
|
||||
state.videoQualityControl.el.on('click',
|
||||
state.videoQualityControl.toggleQuality
|
||||
);
|
||||
state.el.on('play',
|
||||
_.once(state.videoQualityControl.fetchAvailableQualities)
|
||||
);
|
||||
state.el.on('play', _.once(function () {
|
||||
state.videoQualityControl.fetchAvailableQualities();
|
||||
}));
|
||||
}
|
||||
|
||||
// ***************************************************************
|
||||
|
||||
@@ -1,264 +1,438 @@
|
||||
(function (requirejs, require, define) {
|
||||
|
||||
(function(define) {
|
||||
'use strict';
|
||||
// VideoVolumeControl module.
|
||||
define(
|
||||
'video/07_video_volume_control.js',
|
||||
[],
|
||||
function () {
|
||||
|
||||
// VideoVolumeControl() function - what this module "exports".
|
||||
return function (state) {
|
||||
var dfd = $.Deferred();
|
||||
|
||||
if (state.isTouch) {
|
||||
// iOS doesn't support volume change
|
||||
state.el.find('div.volume').remove();
|
||||
dfd.resolve();
|
||||
return dfd.promise();
|
||||
'video/07_video_volume_control.js', [],
|
||||
function() {
|
||||
/**
|
||||
* Video volume control module.
|
||||
* @exports video/07_video_volume_control.js
|
||||
* @constructor
|
||||
* @param {Object} state The object containing the state of the video
|
||||
* @param {Object} i18n The object containing strings with translations.
|
||||
* @return {jquery Promise}
|
||||
*/
|
||||
var VolumeControl = function(state, i18n) {
|
||||
if (!(this instanceof VolumeControl)) {
|
||||
return new VolumeControl(state, i18n);
|
||||
}
|
||||
|
||||
state.videoVolumeControl = {};
|
||||
this.state = state;
|
||||
this.state.videoVolumeControl = this;
|
||||
this.i18n = i18n;
|
||||
this.initialize();
|
||||
|
||||
_makeFunctionsPublic(state);
|
||||
_renderElements(state);
|
||||
_bindHandlers(state);
|
||||
|
||||
dfd.resolve();
|
||||
return dfd.promise();
|
||||
return $.Deferred().resolve().promise();
|
||||
};
|
||||
|
||||
// ***************************************************************
|
||||
// Private functions start here.
|
||||
// ***************************************************************
|
||||
VolumeControl.prototype = {
|
||||
/** Minimum value for the volume slider. */
|
||||
min: 0,
|
||||
/** Maximum value for the volume slider. */
|
||||
max: 100,
|
||||
/** Step to increase/decrease volume level via keyboard. */
|
||||
step: 20,
|
||||
|
||||
// function _makeFunctionsPublic(state)
|
||||
//
|
||||
// Functions which will be accessible via 'state' object. When called, these functions will
|
||||
// get the 'state' object as a context.
|
||||
function _makeFunctionsPublic(state) {
|
||||
var methodsDict = {
|
||||
onChange: onChange,
|
||||
toggleMute: toggleMute
|
||||
};
|
||||
/** Initializes the module. */
|
||||
initialize: function() {
|
||||
var volume;
|
||||
|
||||
state.bindTo(methodsDict, state.videoVolumeControl, state);
|
||||
}
|
||||
this.el = this.state.el.find('.volume');
|
||||
|
||||
// function _renderElements(state)
|
||||
//
|
||||
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
|
||||
// make the created DOM elements available via the 'state' object. Much easier to work this
|
||||
// way - you don't have to do repeated jQuery element selects.
|
||||
function _renderElements(state) {
|
||||
var volumeControl = state.videoVolumeControl,
|
||||
element = state.el.find('div.volume'),
|
||||
button = element.find('a'),
|
||||
volumeSlider = element.find('.volume-slider'),
|
||||
// Figure out what the current volume is. If no information about
|
||||
// volume level could be retrieved, then we will use the default 100
|
||||
// level (full volume).
|
||||
currentVolume = parseInt($.cookie('video_player_volume_level'), 10),
|
||||
// Set it up so that muting/unmuting works correctly.
|
||||
previousVolume = 100,
|
||||
slider, buttonStr, volumeSliderHandleEl;
|
||||
if (this.state.isTouch) {
|
||||
// iOS doesn't support volume change
|
||||
this.el.remove();
|
||||
return false;
|
||||
}
|
||||
// Youtube iframe react on key buttons and has his own handlers.
|
||||
// So, we disallow focusing on iframe.
|
||||
this.state.el.find('iframe').attr('tabindex', -1);
|
||||
this.button = this.el.children('a');
|
||||
this.cookie = new CookieManager(this.min, this.max);
|
||||
this.a11y = new Accessibility(
|
||||
this.button, this.min, this.max, this.i18n
|
||||
);
|
||||
volume = this.cookie.getVolume();
|
||||
this.storedVolume = this.max;
|
||||
|
||||
if (!isFinite(currentVolume)) {
|
||||
currentVolume = 100;
|
||||
this.render();
|
||||
this.bindHandlers();
|
||||
this.setVolume(volume, true, false);
|
||||
this.checkMuteButtonStatus(volume);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates any necessary DOM elements, attach them, and set their,
|
||||
* initial configuration.
|
||||
*/
|
||||
render: function() {
|
||||
var container = this.el.find('.volume-slider');
|
||||
|
||||
this.volumeSlider = container.slider({
|
||||
orientation: 'vertical',
|
||||
range: 'min',
|
||||
min: this.min,
|
||||
max: this.max,
|
||||
slide: this.onSlideHandler.bind(this)
|
||||
});
|
||||
|
||||
// We provide an independent behavior to adjust volume level.
|
||||
// Therefore, we do not need redundant focusing on slider in TAB
|
||||
// order.
|
||||
container.find('a').attr('tabindex', -1);
|
||||
},
|
||||
|
||||
/** Bind any necessary function callbacks to DOM events. */
|
||||
bindHandlers: function() {
|
||||
this.state.el.on({
|
||||
'keydown': this.keyDownHandler.bind(this),
|
||||
'play': _.once(this.updateVolumeSilently.bind(this)),
|
||||
'volumechange': this.onVolumeChangeHandler.bind(this)
|
||||
});
|
||||
this.el.on({
|
||||
'mouseenter': this.openMenu.bind(this),
|
||||
'mouseleave': this.closeMenu.bind(this)
|
||||
});
|
||||
this.button.on({
|
||||
'click': false,
|
||||
'mousedown': this.toggleMuteHandler.bind(this),
|
||||
'keydown': this.keyDownButtonHandler.bind(this),
|
||||
'focus': this.openMenu.bind(this),
|
||||
'blur': this.closeMenu.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates volume level without updating view and triggering
|
||||
* `volumechange` event.
|
||||
*/
|
||||
updateVolumeSilently: function() {
|
||||
this.state.el.trigger(
|
||||
'volumechange:silent', [this.getVolume()]
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns current volume level.
|
||||
* @return {Number}
|
||||
*/
|
||||
getVolume: function() {
|
||||
return this.volume;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets current volume level.
|
||||
* @param {Number} volume Suggested volume level
|
||||
* @param {Boolean} [silent] Sets the new volume level without
|
||||
* triggering `volumechange` event and updating the cookie.
|
||||
* @param {Boolean} [withoutSlider] Disables updating the slider.
|
||||
*/
|
||||
setVolume: function(volume, silent, withoutSlider) {
|
||||
if (volume === this.getVolume()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.volume = volume;
|
||||
this.a11y.update(this.getVolume());
|
||||
|
||||
if (!withoutSlider) {
|
||||
this.updateSliderView(this.getVolume());
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
this.cookie.setVolume(this.getVolume());
|
||||
this.state.el.trigger('volumechange', [this.getVolume()]);
|
||||
}
|
||||
},
|
||||
|
||||
/** Increases current volume level using previously defined step. */
|
||||
increaseVolume: function() {
|
||||
var volume = Math.min(this.getVolume() + this.step, this.max);
|
||||
|
||||
this.setVolume(volume, false, false);
|
||||
},
|
||||
|
||||
/** Decreases current volume level using previously defined step. */
|
||||
decreaseVolume: function() {
|
||||
var volume = Math.max(this.getVolume() - this.step, this.min);
|
||||
|
||||
this.setVolume(volume, false, false);
|
||||
},
|
||||
|
||||
/** Updates volume slider view. */
|
||||
updateSliderView: function (volume) {
|
||||
this.volumeSlider.slider('value', volume);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mutes or unmutes volume.
|
||||
* @param {Number} muteStatus Flag to mute/unmute volume.
|
||||
*/
|
||||
mute: function(muteStatus) {
|
||||
var volume;
|
||||
|
||||
this.updateMuteButtonView(muteStatus);
|
||||
|
||||
if (muteStatus) {
|
||||
this.storedVolume = this.getVolume() || this.max;
|
||||
}
|
||||
|
||||
volume = muteStatus ? 0 : this.storedVolume;
|
||||
this.setVolume(volume, false, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns current volume state (is it muted or not?).
|
||||
* @return {Boolean}
|
||||
*/
|
||||
getMuteStatus: function () {
|
||||
return this.getVolume() === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the volume button view.
|
||||
* @param {Boolean} isMuted Flag to use muted or unmuted view.
|
||||
*/
|
||||
updateMuteButtonView: function(isMuted) {
|
||||
var action = isMuted ? 'addClass' : 'removeClass';
|
||||
|
||||
this.el[action]('is-muted');
|
||||
},
|
||||
|
||||
/** Toggles the state of the volume button. */
|
||||
toggleMute: function() {
|
||||
this.mute(!this.getMuteStatus());
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks and updates the state of the volume button relatively to
|
||||
* volume level.
|
||||
* @param {Number} volume Volume level.
|
||||
*/
|
||||
checkMuteButtonStatus: function (volume) {
|
||||
if (volume <= this.min) {
|
||||
this.updateMuteButtonView(true);
|
||||
this.state.el.off('volumechange.is-muted');
|
||||
this.state.el.on('volumechange.is-muted', _.once(function () {
|
||||
this.updateMuteButtonView(false);
|
||||
}.bind(this)));
|
||||
}
|
||||
},
|
||||
|
||||
/** Opens volume menu. */
|
||||
openMenu: function() {
|
||||
this.el.addClass('is-opened');
|
||||
},
|
||||
|
||||
/** Closes speed menu. */
|
||||
closeMenu: function() {
|
||||
this.el.removeClass('is-opened');
|
||||
},
|
||||
|
||||
/**
|
||||
* Keydown event handler for the video container.
|
||||
* @param {jquery Event} event
|
||||
*/
|
||||
keyDownHandler: function(event) {
|
||||
// ALT key is used to change (alternate) the function of
|
||||
// other pressed keys. In this case, do nothing.
|
||||
if (event.altKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($(event.target).hasClass('ui-slider-handle')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var KEY = $.ui.keyCode,
|
||||
keyCode = event.keyCode;
|
||||
|
||||
switch (keyCode) {
|
||||
case KEY.UP:
|
||||
// Shift + Arrows keyboard shortcut might be used by
|
||||
// screen readers. In this case, do nothing.
|
||||
if (event.shiftKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.increaseVolume();
|
||||
return false;
|
||||
case KEY.DOWN:
|
||||
// Shift + Arrows keyboard shortcut might be used by
|
||||
// screen readers. In this case, do nothing.
|
||||
if (event.shiftKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.decreaseVolume();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Keydown event handler for the volume button.
|
||||
* @param {jquery Event} event
|
||||
*/
|
||||
keyDownButtonHandler: function(event) {
|
||||
// ALT key is used to change (alternate) the function of
|
||||
// other pressed keys. In this case, do nothing.
|
||||
if (event.altKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var KEY = $.ui.keyCode,
|
||||
keyCode = event.keyCode;
|
||||
|
||||
switch (keyCode) {
|
||||
case KEY.ENTER:
|
||||
case KEY.SPACE:
|
||||
this.toggleMute();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* onSlide callback for the video slider.
|
||||
* @param {jquery Event} event
|
||||
* @param {jqueryuiSlider ui} ui
|
||||
*/
|
||||
onSlideHandler: function(event, ui) {
|
||||
this.setVolume(ui.value, false, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mousedown event handler for the volume button.
|
||||
* @param {jquery Event} event
|
||||
*/
|
||||
toggleMuteHandler: function(event) {
|
||||
this.toggleMute();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
* Volumechange event handler.
|
||||
* @param {jquery Event} event
|
||||
* @param {Number} volume Volume level.
|
||||
*/
|
||||
onVolumeChangeHandler: function(event, volume) {
|
||||
this.checkMuteButtonStatus(volume);
|
||||
}
|
||||
|
||||
slider = volumeSlider.slider({
|
||||
orientation: 'vertical',
|
||||
range: 'min',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: currentVolume,
|
||||
change: volumeControl.onChange,
|
||||
slide: volumeControl.onChange
|
||||
});
|
||||
|
||||
element.toggleClass('muted', currentVolume === 0);
|
||||
|
||||
// ARIA
|
||||
// Let screen readers know that:
|
||||
// This anchor behaves as a button named 'Volume'.
|
||||
buttonStr = (currentVolume === 0) ? 'Volume muted' : 'Volume';
|
||||
// We add the aria-label attribute because the title attribute cannot be
|
||||
// read.
|
||||
button.attr('aria-label', gettext(buttonStr));
|
||||
|
||||
// Let screen readers know that this anchor, representing the slider
|
||||
// handle, behaves as a slider named 'volume'.
|
||||
volumeSliderHandleEl = slider.find('.ui-slider-handle');
|
||||
|
||||
volumeSliderHandleEl.attr({
|
||||
'role': 'slider',
|
||||
'title': gettext('Volume'),
|
||||
'aria-disabled': false,
|
||||
'aria-valuemin': slider.slider('option', 'min'),
|
||||
'aria-valuemax': slider.slider('option', 'max'),
|
||||
'aria-valuenow': slider.slider('option', 'value'),
|
||||
'aria-valuetext': getVolumeDescription(slider.slider('option', 'value'))
|
||||
});
|
||||
|
||||
|
||||
state.currentVolume = currentVolume;
|
||||
$.extend(state.videoVolumeControl, {
|
||||
el: element,
|
||||
buttonEl: button,
|
||||
volumeSliderEl: volumeSlider,
|
||||
currentVolume: currentVolume,
|
||||
previousVolume: previousVolume,
|
||||
slider: slider,
|
||||
volumeSliderHandleEl: volumeSliderHandleEl
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Bind any necessary function callbacks to DOM events (click,
|
||||
* mousemove, etc.).
|
||||
*
|
||||
* @type {function}
|
||||
* @access private
|
||||
*
|
||||
* @param {object} state The object containg the state of the video player.
|
||||
* All other modules, their parameters, public variables, etc. are
|
||||
* available via this object.
|
||||
*
|
||||
* @this {object} The global window object.
|
||||
*
|
||||
* @returns {undefined}
|
||||
* Module responsible for the accessibility of volume controls.
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {jquery $} button The volume button.
|
||||
* @param {Number} min Minimum value for the volume slider.
|
||||
* @param {Number} max Maximum value for the volume slider.
|
||||
* @param {Object} i18n The object containing strings with translations.
|
||||
*/
|
||||
function _bindHandlers(state) {
|
||||
state.videoVolumeControl.buttonEl
|
||||
.on('click', state.videoVolumeControl.toggleMute);
|
||||
var Accessibility = function (button, min, max, i18n) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.button = button;
|
||||
this.i18n = i18n;
|
||||
|
||||
state.videoVolumeControl.el.on('mouseenter', function() {
|
||||
state.videoVolumeControl.el.addClass('open');
|
||||
});
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
state.videoVolumeControl.el.on('mouseleave', function() {
|
||||
state.videoVolumeControl.el.removeClass('open');
|
||||
});
|
||||
Accessibility.prototype = {
|
||||
/** Initializes the module. */
|
||||
initialize: function() {
|
||||
this.liveRegion = $('<div />', {
|
||||
'class': 'sr video-live-region',
|
||||
'role': 'status',
|
||||
'aria-hidden': 'false',
|
||||
'aria-live': 'polite',
|
||||
'aria-atomic': 'false'
|
||||
});
|
||||
|
||||
// Attach a focus event to the volume button.
|
||||
state.videoVolumeControl.buttonEl.on('blur', function() {
|
||||
// If the focus is being trasnfered from the volume slider, then we
|
||||
// don't do anything except for unsetting the special flag.
|
||||
if (state.volumeBlur === true) {
|
||||
state.volumeBlur = false;
|
||||
this.button.after(this.liveRegion);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates text of the live region.
|
||||
* @param {Number} volume Volume level.
|
||||
*/
|
||||
update: function(volume) {
|
||||
this.liveRegion.text([
|
||||
this.getVolumeDescription(volume),
|
||||
this.i18n['Volume'] + '.'
|
||||
].join(' '));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a string describing the level of volume.
|
||||
* @param {Number} volume Volume level.
|
||||
*/
|
||||
getVolumeDescription: function(volume) {
|
||||
if (volume === 0) {
|
||||
return this.i18n['Muted'];
|
||||
} else if (volume <= 20) {
|
||||
return this.i18n['Very low'];
|
||||
} else if (volume <= 40) {
|
||||
return this.i18n['Low'];
|
||||
} else if (volume <= 60) {
|
||||
return this.i18n['Average'];
|
||||
} else if (volume <= 80) {
|
||||
return this.i18n['Loud'];
|
||||
} else if (volume <= 99) {
|
||||
return this.i18n['Very loud'];
|
||||
}
|
||||
|
||||
//If the focus is comming from elsewhere, then we must show the
|
||||
// volume slider and set focus to it.
|
||||
else {
|
||||
state.videoVolumeControl.el.addClass('open');
|
||||
state.videoVolumeControl.volumeSliderEl.find('a').focus();
|
||||
return this.i18n['Maximum'];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Module responsible for the work with volume cookie.
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {Number} min Minimum value for the volume slider.
|
||||
* @param {Number} max Maximum value for the volume slider.
|
||||
*/
|
||||
var CookieManager = function (min, max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.cookieName = 'video_player_volume_level';
|
||||
};
|
||||
|
||||
CookieManager.prototype = {
|
||||
/**
|
||||
* Returns volume level from the cookie.
|
||||
* @return {Number} Volume level.
|
||||
*/
|
||||
getVolume: function() {
|
||||
var volume = parseInt($.cookie(this.cookieName), 10);
|
||||
|
||||
if (_.isFinite(volume)) {
|
||||
volume = Math.max(volume, this.min);
|
||||
volume = Math.min(volume, this.max);
|
||||
} else {
|
||||
volume = this.max;
|
||||
}
|
||||
});
|
||||
|
||||
// Attach a blur event handler (loss of focus) to the volume slider
|
||||
// element. More specifically, we are attaching to the handle on
|
||||
// the slider with which you can change the volume.
|
||||
state.videoVolumeControl.volumeSliderEl.find('a')
|
||||
.on('blur', function () {
|
||||
// Hide the volume slider. This is done so that we can
|
||||
// continue to the next (or previous) element by tabbing.
|
||||
// Otherwise, after next tab we would come back to the volume
|
||||
// slider because it is the next element visible element that
|
||||
// we can tab to after the volume button.
|
||||
state.videoVolumeControl.el.removeClass('open');
|
||||
return volume;
|
||||
},
|
||||
|
||||
// Set focus to the volume button.
|
||||
state.videoVolumeControl.buttonEl.focus();
|
||||
|
||||
// We store the fact that previous element that lost focus was
|
||||
// the volume clontrol.
|
||||
state.volumeBlur = true;
|
||||
// The following field is used in video_speed_control to track
|
||||
// the element that had the focus before it.
|
||||
state.previousFocus = 'volume';
|
||||
});
|
||||
}
|
||||
|
||||
// ***************************************************************
|
||||
// Public functions start here.
|
||||
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
|
||||
// The magic private function that makes them available and sets up their context is makeFunctionsPublic().
|
||||
// ***************************************************************
|
||||
|
||||
function onChange(event, ui) {
|
||||
var currentVolume = ui.value,
|
||||
ariaLabelText = (currentVolume === 0) ? 'Volume muted' : 'Volume';
|
||||
|
||||
this.videoVolumeControl.currentVolume = currentVolume;
|
||||
this.videoVolumeControl.el.toggleClass('muted', currentVolume === 0);
|
||||
|
||||
$.cookie('video_player_volume_level', ui.value, {
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
|
||||
this.trigger('videoPlayer.onVolumeChange', ui.value);
|
||||
|
||||
// ARIA
|
||||
this.videoVolumeControl.volumeSliderHandleEl.attr({
|
||||
'aria-valuenow': ui.value,
|
||||
'aria-valuetext': getVolumeDescription(ui.value)
|
||||
});
|
||||
|
||||
this.videoVolumeControl.buttonEl.attr(
|
||||
'aria-label', gettext(ariaLabelText)
|
||||
);
|
||||
}
|
||||
|
||||
function toggleMute(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.videoVolumeControl.currentVolume > 0) {
|
||||
this.videoVolumeControl.previousVolume = this.videoVolumeControl.currentVolume;
|
||||
this.videoVolumeControl.slider.slider('option', 'value', 0);
|
||||
// ARIA
|
||||
this.videoVolumeControl.volumeSliderHandleEl.attr({
|
||||
'aria-valuenow': 0,
|
||||
'aria-valuetext': getVolumeDescription(0),
|
||||
});
|
||||
} else {
|
||||
this.videoVolumeControl.slider.slider('option', 'value', this.videoVolumeControl.previousVolume);
|
||||
// ARIA
|
||||
this.videoVolumeControl.volumeSliderHandleEl.attr({
|
||||
'aria-valuenow': this.videoVolumeControl.previousVolume,
|
||||
'aria-valuetext': getVolumeDescription(this.videoVolumeControl.previousVolume)
|
||||
/**
|
||||
* Updates volume cookie.
|
||||
* @param {Number} volume Volume level.
|
||||
*/
|
||||
setVolume: function(value) {
|
||||
$.cookie(this.cookieName, value, {
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ARIA
|
||||
// Returns a string describing the level of volume.
|
||||
function getVolumeDescription(vol) {
|
||||
if (vol === 0) {
|
||||
// Translators: Volume level equals 0%.
|
||||
return gettext('Muted');
|
||||
} else if (vol <= 20) {
|
||||
// Translators: Volume level in range (0,20]%
|
||||
return gettext('Very low');
|
||||
} else if (vol <= 40) {
|
||||
// Translators: Volume level in range (20,40]%
|
||||
return gettext('Low');
|
||||
} else if (vol <= 60) {
|
||||
// Translators: Volume level in range (40,60]%
|
||||
return gettext('Average');
|
||||
} else if (vol <= 80) {
|
||||
// Translators: Volume level in range (60,80]%
|
||||
return gettext('Loud');
|
||||
} else if (vol <= 99) {
|
||||
// Translators: Volume level in range (80,100)%
|
||||
return gettext('Very loud');
|
||||
}
|
||||
|
||||
// Translators: Volume level equals 100%.
|
||||
return gettext('Maximum');
|
||||
}
|
||||
};
|
||||
|
||||
return VolumeControl;
|
||||
});
|
||||
|
||||
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
|
||||
}(RequireJS.define));
|
||||
|
||||
@@ -9,6 +9,7 @@ function (Iterator) {
|
||||
* @exports video/08_video_speed_control.js
|
||||
* @constructor
|
||||
* @param {object} state The object containing the state of the video player.
|
||||
* @return {jquery Promise}
|
||||
*/
|
||||
var SpeedControl = function (state) {
|
||||
if (!(this instanceof SpeedControl)) {
|
||||
|
||||
@@ -71,6 +71,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
|
||||
resource_string(module, 'js/src/video/00_video_storage.js'),
|
||||
resource_string(module, 'js/src/video/00_resizer.js'),
|
||||
resource_string(module, 'js/src/video/00_async_process.js'),
|
||||
resource_string(module, 'js/src/video/00_i18n.js'),
|
||||
resource_string(module, 'js/src/video/00_sjson.js'),
|
||||
resource_string(module, 'js/src/video/00_iterator.js'),
|
||||
resource_string(module, 'js/src/video/01_initialize.js'),
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
<ol class="video-speeds menu" role="menu"></ol>
|
||||
</div>
|
||||
<div class="volume">
|
||||
<a href="#" title="${_('Volume')}" role="button" aria-disabled="false"></a>
|
||||
<div class="volume-slider-container">
|
||||
<a href="#" role="button" aria-disabled="false" title="${_('Volume')}" aria-label="${_('Click on this button to mute or unmute this video or press UP or DOWN buttons to increase or decrease volume level.')}"></a>
|
||||
<div role="presentation" class="volume-slider-container">
|
||||
<div class="volume-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user