Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
c08e21c3
Commit
c08e21c3
authored
May 26, 2014
by
Anton Stupak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3678 from edx/anton/refactor-volume-control
Video: refactor volume control.
parents
d01af063
8c26fc68
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
645 additions
and
342 deletions
+645
-342
common/lib/xmodule/xmodule/css/video/display.scss
+2
-2
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+5
-14
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
+203
-99
common/lib/xmodule/xmodule/js/src/video/00_i18n.js
+31
-0
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+5
-6
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+4
-2
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
+3
-3
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
+388
-214
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+1
-0
common/lib/xmodule/xmodule/video_module/video_module.py
+1
-0
lms/templates/video.html
+2
-2
No files found.
common/lib/xmodule/xmodule/css/video/display.scss
View file @
c08e21c3
...
...
@@ -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')
;
}
...
...
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
c08e21c3
...
...
@@ -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,7 +449,6 @@ function (VideoPlayer) {
},
'currentTime got updated'
,
10000
);
});
});
});
describe
(
'when the video is not playing'
,
function
()
{
beforeEach
(
function
()
{
...
...
@@ -472,20 +470,13 @@ function (VideoPlayer) {
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'
);
});
});
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
);
});
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
View file @
c08e21c3
(
function
(
undefined
)
{
describe
(
'VideoVolumeControl'
,
function
()
{
var
state
,
oldOTBD
;
(
function
()
{
'use strict'
;
describe
(
'VideoVolumeControl'
,
function
()
{
var
state
,
oldOTBD
,
volumeControl
;
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
...
...
@@ -14,56 +15,57 @@
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
()
{
spyOn
(
$
.
fn
,
'slider'
).
andCallThrough
();
$
.
cookie
.
andReturn
(
'75'
);
state
=
jasmine
.
initializePlayer
();
volumeControl
=
state
.
videoVolumeControl
;
});
it
(
'initialize currentV
olume to 75%'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
currentV
olume
).
toEqual
(
75
);
it
(
'initialize v
olume to 75%'
,
function
()
{
expect
(
volumeControl
.
v
olume
).
toEqual
(
75
);
});
it
(
'render the volume control'
,
function
()
{
expect
(
state
.
videoControl
.
secondaryControlsEl
.
html
())
.
toContain
(
"<div class=
\"
volume
\"
>
\
n"
);
.
toContain
(
'<div class="volume">
\
n'
);
});
it
(
'create the slider'
,
function
()
{
expect
(
$
.
fn
.
slider
).
toHaveBeenCalledWith
(
{
orientation
:
"vertical"
,
range
:
"min"
,
expect
(
$
.
fn
.
slider
.
calls
[
2
].
args
).
toEqual
([
{
orientation
:
'vertical'
,
range
:
'min'
,
min
:
0
,
max
:
100
,
value
:
state
.
videoVolumeControl
.
currentVolume
,
change
:
state
.
videoVolumeControl
.
onChange
,
slide
:
state
.
videoVolumeControl
.
onChange
});
slide
:
jasmine
.
any
(
Function
)
}]);
expect
(
$
.
fn
.
slider
).
toHaveBeenCalledWith
(
'value'
,
volumeControl
.
volume
);
});
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'
];
it
(
'add ARIA attributes to live region'
,
function
()
{
var
liveRegion
=
$
(
'.video-live-region'
);
expect
(
sliderHandle
).
toHaveAttrs
({
'role'
:
'slider'
,
'title'
:
'Volume'
,
'aria-disabled'
:
'false'
,
'aria-valuemin'
:
'0'
,
'aria-valuemax'
:
'100'
expect
(
liveRegion
).
toHaveAttrs
({
'role'
:
'status'
,
'aria-live'
:
'polite'
,
'aria-atomic'
:
'false'
});
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'
);
var
button
=
$
(
'.volume >
a'
);
expect
(
volumeControl
).
toHaveAttrs
({
expect
(
button
).
toHaveAttrs
({
'role'
:
'button'
,
'title'
:
'Volume'
,
'aria-disabled'
:
'false'
...
...
@@ -71,139 +73,241 @@
});
it
(
'bind the volume control'
,
function
()
{
expect
(
$
(
'.volume>a'
)).
toHandleWith
(
'click'
,
state
.
videoVolumeControl
.
toggleMute
);
expect
(
$
(
'.volume'
)).
not
.
toHaveClass
(
'open'
);
var
button
=
$
(
'.volume > a'
);
expect
(
button
).
toHandle
(
'keydown'
);
expect
(
button
).
toHandle
(
'mousedown'
);
expect
(
$
(
'.volume'
)).
not
.
toHaveClass
(
'is-opened'
);
$
(
'.volume'
).
mouseenter
();
expect
(
$
(
'.volume'
)).
toHaveClass
(
'open
'
);
expect
(
$
(
'.volume'
)).
toHaveClass
(
'is-opened
'
);
$
(
'.volume'
).
mouseleave
();
expect
(
$
(
'.volume'
)).
not
.
toHaveClass
(
'open'
);
});
});
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'
}];
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
()
{
state
.
videoVolumeControl
.
onChange
(
void
0
,
{
value
:
60
});
volumeControl
.
setVolume
(
60
,
false
,
true
);
});
it
(
'set the player volume'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
currentVolume
).
toEqual
(
60
);
expect
(
volumeControl
.
volume
).
toEqual
(
60
);
});
it
(
'remove muted class'
,
function
()
{
expect
(
$
(
'.volume'
)).
not
.
toHaveClass
(
'is-muted'
);
});
});
it
(
'remote muted class'
,
function
()
{
expect
(
$
(
'.volume'
)).
not
.
toHaveClass
(
'muted'
);
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
(
'when the new volume is 0'
,
function
()
{
beforeEach
(
function
()
{
state
.
videoVolumeControl
.
onChange
(
void
0
,
{
value
:
0
});
volumeControl
.
setVolume
(
0
,
false
,
true
);
});
it
(
'set the player volume'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
currentV
olume
).
toEqual
(
0
);
expect
(
volumeControl
.
v
olume
).
toEqual
(
0
);
});
it
(
'add muted class'
,
function
()
{
expect
(
$
(
'.volume'
)).
toHaveClass
(
'
muted'
);
expect
(
$
(
'.volume'
)).
toHaveClass
(
'is-
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
(
'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
(
'changes ARIA attributes'
,
function
()
{
var
sliderHandle
=
$
(
'div.volume-slider>a.ui-slider-handle'
);
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'
);
});
expect
(
sliderHandle
).
toHaveAttrs
({
'aria-valuenow'
:
data
.
value
.
toString
(
10
),
'aria-valuetext'
:
data
.
expectation
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
()
{
state
.
videoVolumeControl
.
currentV
olume
=
60
;
state
.
videoVolumeControl
.
buttonEl
.
trigger
(
'click
'
);
volumeControl
.
v
olume
=
60
;
volumeControl
.
button
.
trigger
(
'mousedown
'
);
});
it
(
'save the previous volume'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
previous
Volume
).
toEqual
(
60
);
expect
(
volumeControl
.
stored
Volume
).
toEqual
(
60
);
});
it
(
'set the player volume'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
currentV
olume
).
toEqual
(
0
);
expect
(
volumeControl
.
v
olume
).
toEqual
(
0
);
});
});
describe
(
'when the current volume is 0'
,
function
()
{
beforeEach
(
function
()
{
state
.
videoVolumeControl
.
currentV
olume
=
0
;
state
.
videoVolumeControl
.
previous
Volume
=
60
;
state
.
videoVolumeControl
.
buttonEl
.
trigger
(
'click
'
);
volumeControl
.
v
olume
=
0
;
volumeControl
.
stored
Volume
=
60
;
volumeControl
.
button
.
trigger
(
'mousedown
'
);
});
it
(
'set the player volume to previous volume'
,
function
()
{
expect
(
state
.
videoVolumeControl
.
currentV
olume
).
toEqual
(
60
);
expect
(
volumeControl
.
v
olume
).
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
);
common/lib/xmodule/xmodule/js/src/video/00_i18n.js
0 → 100644
View file @
c08e21c3
(
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
));
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
c08e21c3
...
...
@@ -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
,
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
c08e21c3
...
...
@@ -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
);
}
});
...
...
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
View file @
c08e21c3
...
...
@@ -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
();
})
);
}
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
View file @
c08e21c3
(
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.
// ***************************************************************
// 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
};
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
,
state
.
bindTo
(
methodsDict
,
state
.
videoVolumeControl
,
state
);
}
/** Initializes the module. */
initialize
:
function
()
{
var
volume
;
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
(
!
isFinite
(
currentVolume
))
{
currentVolume
=
100
;
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
;
this
.
render
();
this
.
bindHandlers
();
this
.
setVolume
(
volume
,
true
,
false
);
this
.
checkMuteButtonStatus
(
volume
);
},
slider
=
volumeSlider
.
slider
({
/**
* 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
:
0
,
max
:
100
,
value
:
currentVolume
,
change
:
volumeControl
.
onChange
,
slide
:
volumeControl
.
onChange
min
:
this
.
min
,
max
:
this
.
max
,
slide
:
this
.
onSlideHandler
.
bind
(
this
)
});
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'
))
// 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
()]
);
},
state
.
currentVolume
=
currentVolume
;
$
.
extend
(
state
.
videoVolumeControl
,
{
el
:
element
,
buttonEl
:
button
,
volumeSliderEl
:
volumeSlider
,
currentVolume
:
currentVolume
,
previousVolume
:
previousVolume
,
slider
:
slider
,
volumeSliderHandleEl
:
volumeSliderHandleEl
});
/**
* 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
);
},
/**
* @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}
* Mutes or unmutes volume.
* @param {Number} muteStatus Flag to mute/unmute volume.
*/
function
_bindHandlers
(
state
)
{
state
.
videoVolumeControl
.
buttonEl
.
on
(
'click'
,
state
.
videoVolumeControl
.
toggleMute
);
mute
:
function
(
muteStatus
)
{
var
volume
;
state
.
videoVolumeControl
.
el
.
on
(
'mouseenter'
,
function
()
{
state
.
videoVolumeControl
.
el
.
addClass
(
'open'
);
});
this
.
updateMuteButtonView
(
muteStatus
);
state
.
videoVolumeControl
.
el
.
on
(
'mouseleave'
,
function
()
{
state
.
videoVolumeControl
.
el
.
removeClass
(
'open'
);
});
if
(
muteStatus
)
{
this
.
storedVolume
=
this
.
getVolume
()
||
this
.
max
;
}
volume
=
muteStatus
?
0
:
this
.
storedVolume
;
this
.
setVolume
(
volume
,
false
,
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
;
/**
* 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'
);
},
//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
();
/** 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
;
}
});
// 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'
);
// 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'
;
});
if
(
$
(
event
.
target
).
hasClass
(
'ui-slider-handle'
))
{
return
true
;
}
// ***************************************************************
// 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().
// ***************************************************************
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
;
function
onChange
(
event
,
ui
)
{
var
currentVolume
=
ui
.
value
,
ariaLabelText
=
(
currentVolume
===
0
)
?
'Volume muted'
:
'Volume'
;
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
.
videoVolumeControl
.
currentVolume
=
currentVolume
;
this
.
videoVolumeControl
.
el
.
toggleClass
(
'muted'
,
currentVolume
===
0
);
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
;
}
$
.
cookie
(
'video_player_volume_level'
,
ui
.
value
,
{
expires
:
3650
,
path
:
'/'
});
this
.
decreaseVolume
();
return
false
;
}
this
.
trigger
(
'videoPlayer.onVolumeChange'
,
ui
.
value
);
return
true
;
},
// ARIA
this
.
videoVolumeControl
.
volumeSliderHandleEl
.
attr
({
'aria-valuenow'
:
ui
.
value
,
'aria-valuetext'
:
getVolumeDescription
(
ui
.
value
)
});
/**
* 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
;
}
this
.
videoVolumeControl
.
buttonEl
.
attr
(
'aria-label'
,
gettext
(
ariaLabelText
)
);
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
;
switch
(
keyCode
)
{
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
this
.
toggleMute
();
return
false
;
}
function
toggleMute
(
event
)
{
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
();
},
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
)
/**
* Volumechange event handler.
* @param {jquery Event} event
* @param {Number} volume Volume level.
*/
onVolumeChangeHandler
:
function
(
event
,
volume
)
{
this
.
checkMuteButtonStatus
(
volume
);
}
};
/**
* 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.
*/
var
Accessibility
=
function
(
button
,
min
,
max
,
i18n
)
{
this
.
min
=
min
;
this
.
max
=
max
;
this
.
button
=
button
;
this
.
i18n
=
i18n
;
this
.
initialize
();
};
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'
});
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'
];
}
return
this
.
i18n
[
'Maximum'
];
}
};
// 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'
);
/**
* 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
;
}
// Translators: Volume level equals 100%.
return
gettext
(
'Maximum'
);
return
volume
;
},
/**
* Updates volume cookie.
* @param {Number} volume Volume level.
*/
setVolume
:
function
(
value
)
{
$
.
cookie
(
this
.
cookieName
,
value
,
{
expires
:
3650
,
path
:
'/'
});
}
};
return
VolumeControl
;
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
}(
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
c08e21c3
...
...
@@ -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
))
{
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
c08e21c3
...
...
@@ -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'
),
...
...
lms/templates/video.html
View file @
c08e21c3
...
...
@@ -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>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment