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
24b631f7
Commit
24b631f7
authored
Apr 11, 2014
by
Anton Stupak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3231 from edx/anton/refactor-speed-control
Video: Refactor speed control.
parents
fe4bd4cb
c0110c2b
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
596 additions
and
471 deletions
+596
-471
cms/djangoapps/contentstore/features/video-editor.py
+1
-1
common/lib/xmodule/xmodule/css/video/display.scss
+11
-8
common/lib/xmodule/xmodule/js/fixtures/video.html
+4
-4
common/lib/xmodule/xmodule/js/fixtures/video_all.html
+4
-4
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
+12
-12
common/lib/xmodule/xmodule/js/spec/video/iterator_spec.js
+103
-0
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
+4
-4
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+0
-2
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
+59
-98
common/lib/xmodule/xmodule/js/src/video/00_iterator.js
+90
-0
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+8
-3
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+7
-11
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
+0
-2
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+278
-306
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
+5
-5
common/lib/xmodule/xmodule/video_module/video_module.py
+1
-0
lms/djangoapps/courseware/features/video.py
+5
-7
lms/templates/video.html
+4
-4
No files found.
cms/djangoapps/contentstore/features/video-editor.py
View file @
24b631f7
...
...
@@ -102,7 +102,7 @@ def choose_new_lang(lang_code):
def
open_menu
(
menu
):
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('
open
')"
.
format
(
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('
is-opened
')"
.
format
(
selector
=
VIDEO_MENUS
[
menu
]
))
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
24b631f7
...
...
@@ -317,7 +317,10 @@ div.video {
div
.secondary-controls
{
float
:
right
;
div
.speeds
>
a
,
div
.volume
>
a
,
a
.add-fullscreen
,
a
.quality_control
,
a
.speed-button
,
div
.volume
>
a
,
a
.add-fullscreen
,
a
.quality_control
,
a
.hide-subtitles
{
// overflow is used to bypass Firefox CSS :focus outline bug
// http://johndoesdesign.com/blog/2012/css/firefox-and-its-css-focus-outline-bug/
...
...
@@ -334,7 +337,7 @@ div.video {
float
:
left
;
position
:
relative
;
&
.
open
{
&
.
is-opened
{
.menu
{
display
:
block
;
opacity
:
1
;
...
...
@@ -377,7 +380,7 @@ div.video {
}
}
&
.active
{
&
.
is-
active
{
a
{
font-weight
:
bold
;
}
...
...
@@ -393,8 +396,8 @@ div.video {
}
div
.speeds
{
&
.
open
{
&
>
a
{
&
.
is-opened
{
.speed-button
{
background-image
:
url('../images/open-arrow.png')
;
}
}
...
...
@@ -407,7 +410,7 @@ div.video {
}
}
&
>
a
{
.speed-button
{
@extend
%video-button
;
@include
clearfix
();
background-image
:
url('../images/closed-arrow.png')
;
...
...
@@ -421,7 +424,7 @@ div.video {
width
:
60px
;
}
h3
{
.label
{
float
:
left
;
font-size
:
em
(
14
);
font-weight
:
normal
;
...
...
@@ -436,7 +439,7 @@ div.video {
}
}
p
.activ
e
{
.valu
e
{
float
:
left
;
font-weight
:
bold
;
margin-bottom
:
0
;
...
...
common/lib/xmodule/xmodule/js/fixtures/video.html
View file @
24b631f7
...
...
@@ -41,11 +41,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds"
>
<a
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
<
h3>
Speed
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
<
span
class=
"label"
>
Speed
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds"
></ol>
<ol
class=
"video
-
speeds"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
title=
"Volume"
role=
"button"
aria-disabled=
"false"
></a>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_all.html
View file @
24b631f7
...
...
@@ -44,11 +44,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds"
>
<a
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
>
<
h3>
Speed
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
<
span
class=
"label"
>
Speed
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds"
></ol>
<ol
class=
"video
-
speeds"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
title=
"Volume"
role=
"button"
aria-disabled=
"false"
></a>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
View file @
24b631f7
...
...
@@ -41,11 +41,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds"
>
<a
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
<
h3>
Speed
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false"
>
<
span
class=
"label"
>
Speed
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds"
></ol>
<ol
class=
"video
-
speeds"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
title=
"Volume"
role=
"button"
aria-disabled=
"false"
></a>
...
...
@@ -108,11 +108,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds"
>
<a
href=
"#
"
>
<
h3>
Speed
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false
"
>
<
span
class=
"label"
>
Speed
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds"
></ol>
<ol
class=
"video
-
speeds"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
></a>
...
...
@@ -173,11 +173,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds"
>
<a
href=
"#
"
>
<
h3>
Speed
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"Speeds"
role=
"button"
aria-disabled=
"false
"
>
<
span
class=
"label"
>
Speed
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds"
></ol>
<ol
class=
"video
-
speeds"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
></a>
...
...
common/lib/xmodule/xmodule/js/spec/video/iterator_spec.js
0 → 100644
View file @
24b631f7
(
function
(
require
)
{
require
(
[
'video/00_iterator.js'
],
function
(
Iterator
)
{
describe
(
'Iterator'
,
function
()
{
var
list
=
[
'a'
,
'b'
,
'c'
,
'd'
,
'e'
],
iterator
;
beforeEach
(
function
()
{
iterator
=
new
Iterator
(
list
);
});
it
(
'size contains correct list length'
,
function
()
{
expect
(
iterator
.
size
).
toBe
(
list
.
length
);
expect
(
iterator
.
lastIndex
).
toBe
(
list
.
length
-
1
);
});
describe
(
'next'
,
function
()
{
describe
(
'with passed `index`'
,
function
()
{
it
(
'returns next item in the list'
,
function
()
{
expect
(
iterator
.
next
(
2
)).
toBe
(
'd'
);
expect
(
iterator
.
next
(
0
)).
toBe
(
'b'
);
});
it
(
'returns first item if index equal last item'
,
function
()
{
expect
(
iterator
.
next
(
4
)).
toBe
(
'a'
);
});
it
(
'returns next item if index is not valid'
,
function
()
{
expect
(
iterator
.
next
(
-
4
)).
toBe
(
'b'
);
// index < 0
expect
(
iterator
.
next
(
100
)).
toBe
(
'c'
);
// index > size
expect
(
iterator
.
next
(
'99'
)).
toBe
(
'd'
);
// incorrect Type
});
});
describe
(
'without passed `index`'
,
function
()
{
it
(
'returns next item in the list'
,
function
()
{
expect
(
iterator
.
next
()).
toBe
(
'b'
);
expect
(
iterator
.
next
()).
toBe
(
'c'
);
});
it
(
'returns first item if index equal last item'
,
function
()
{
expect
(
iterator
.
next
()).
toBe
(
'b'
);
expect
(
iterator
.
next
()).
toBe
(
'c'
);
expect
(
iterator
.
next
()).
toBe
(
'd'
);
expect
(
iterator
.
next
()).
toBe
(
'e'
);
expect
(
iterator
.
next
()).
toBe
(
'a'
);
});
});
});
describe
(
'prev'
,
function
()
{
describe
(
'with passed `index`'
,
function
()
{
it
(
'returns previous item in the list'
,
function
()
{
expect
(
iterator
.
prev
(
3
)).
toBe
(
'c'
);
expect
(
iterator
.
prev
(
1
)).
toBe
(
'a'
);
});
it
(
'returns last item if index equal first item'
,
function
()
{
expect
(
iterator
.
prev
(
0
)).
toBe
(
'e'
);
});
it
(
'returns previous item if index is not valid'
,
function
()
{
expect
(
iterator
.
prev
(
-
4
)).
toBe
(
'e'
);
// index < 0
expect
(
iterator
.
prev
(
100
)).
toBe
(
'd'
);
// index > size
expect
(
iterator
.
prev
(
'99'
)).
toBe
(
'c'
);
// incorrect Type
});
});
describe
(
'without passed `index`'
,
function
()
{
it
(
'returns previous item in the list'
,
function
()
{
expect
(
iterator
.
prev
()).
toBe
(
'e'
);
expect
(
iterator
.
prev
()).
toBe
(
'd'
);
});
it
(
'returns last item if index equal first item'
,
function
()
{
expect
(
iterator
.
prev
()).
toBe
(
'e'
);
});
});
});
it
(
'returns last item in the list'
,
function
()
{
expect
(
iterator
.
last
()).
toBe
(
'e'
);
});
it
(
'returns first item in the list'
,
function
()
{
expect
(
iterator
.
first
()).
toBe
(
'a'
);
});
it
(
'isEnd works correctly'
,
function
()
{
expect
(
iterator
.
isEnd
()).
toBeFalsy
();
iterator
.
next
();
// => index 1
expect
(
iterator
.
isEnd
()).
toBeFalsy
();
iterator
.
next
();
// => index 2
expect
(
iterator
.
isEnd
()).
toBeFalsy
();
iterator
.
next
();
// => index 3
expect
(
iterator
.
isEnd
()).
toBeFalsy
();
iterator
.
next
();
// => index 4 == last
expect
(
iterator
.
isEnd
()).
toBeTruthy
();
});
});
});
}(
RequireJS
.
require
));
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
View file @
24b631f7
...
...
@@ -180,7 +180,7 @@
expect
(
state
.
lang
).
toBe
(
'de'
);
expect
(
state
.
storage
.
setItem
)
.
toHaveBeenCalledWith
(
'language'
,
'de'
);
expect
(
$
(
'.langs-list li.active'
).
length
).
toBe
(
1
);
expect
(
$
(
'.langs-list li.
is-
active'
).
length
).
toBe
(
1
);
});
it
(
'when clicking on link with current language'
,
function
()
{
...
...
@@ -198,15 +198,15 @@
expect
(
state
.
lang
).
toBe
(
'en'
);
expect
(
state
.
storage
.
setItem
)
.
not
.
toHaveBeenCalledWith
(
'language'
,
'en'
);
expect
(
$
(
'.langs-list li.active'
).
length
).
toBe
(
1
);
expect
(
$
(
'.langs-list li.
is-
active'
).
length
).
toBe
(
1
);
});
it
(
'open the language toggle on hover'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
$
(
'.lang'
).
mouseenter
();
expect
(
$
(
'.lang'
)).
toHaveClass
(
'
open
'
);
expect
(
$
(
'.lang'
)).
toHaveClass
(
'
is-opened
'
);
$
(
'.lang'
).
mouseleave
();
expect
(
$
(
'.lang'
)).
not
.
toHaveClass
(
'
open
'
);
expect
(
$
(
'.lang'
)).
not
.
toHaveClass
(
'
is-opened
'
);
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
24b631f7
...
...
@@ -52,8 +52,6 @@ function (VideoPlayer) {
it
(
'create video speed control'
,
function
()
{
expect
(
state
.
videoSpeedControl
).
toBeDefined
();
expect
(
state
.
videoSpeedControl
.
el
).
toHaveClass
(
'speeds'
);
expect
(
state
.
videoSpeedControl
.
speeds
)
.
toEqual
([
'0.75'
,
'1.0'
,
'1.25'
,
'1.50'
]);
expect
(
state
.
speed
).
toEqual
(
'1.50'
);
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
View file @
24b631f7
...
...
@@ -21,26 +21,24 @@
});
it
(
'add the video speed control to player'
,
function
()
{
var
li
,
secondaryControls
;
secondaryControls
=
$
(
'.secondary-controls'
);
li
=
secondaryControls
.
find
(
'.video_speeds li'
);
var
secondaryControls
=
$
(
'.secondary-controls'
),
li
=
secondaryControls
.
find
(
'.video-speeds li'
);
expect
(
secondaryControls
).
toContain
(
'.speeds'
);
expect
(
secondaryControls
).
toContain
(
'.video
_
speeds'
);
expect
(
secondaryControls
.
find
(
'
p.activ
e'
).
text
())
expect
(
secondaryControls
).
toContain
(
'.video
-
speeds'
);
expect
(
secondaryControls
.
find
(
'
.valu
e'
).
text
())
.
toBe
(
'1.50x'
);
expect
(
li
.
filter
(
'.active'
)).
toHaveData
(
expect
(
li
.
filter
(
'.
is-
active'
)).
toHaveData
(
'speed'
,
state
.
videoSpeedControl
.
currentSpeed
);
expect
(
li
.
length
).
toBe
(
state
.
videoSpeedControl
.
speeds
.
length
);
expect
(
li
.
length
).
toBe
(
state
.
speeds
.
length
);
$
.
each
(
li
.
toArray
().
reverse
(),
function
(
index
,
link
)
{
expect
(
$
(
link
)).
toHaveData
(
'speed'
,
state
.
videoSpeedControl
.
speeds
[
index
]
'speed'
,
state
.
speeds
[
index
]
);
expect
(
$
(
link
).
find
(
'a'
).
text
()).
toBe
(
state
.
videoSpeedControl
.
speeds
[
index
]
+
'x'
state
.
speeds
[
index
]
+
'x'
);
});
});
...
...
@@ -68,23 +66,13 @@
});
describe
(
'when running on non-touch based device'
,
function
()
{
var
speedControl
,
speedEntries
,
var
speedControl
,
speedEntries
,
speedButton
,
KEY
=
$
.
ui
.
keyCode
,
keyPressEvent
=
function
(
key
)
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
key
});
},
tabBackPressEvent
=
function
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
KEY
.
TAB
,
shiftKey
:
true
});
},
tabForwardPressEvent
=
function
()
{
return
$
.
Event
(
'keydown'
,
{
keyCode
:
KEY
.
TAB
,
shiftKey
:
false
});
},
// Get previous element in array or cyles back to the last
// if it is the first.
previousSpeed
=
function
(
index
)
{
...
...
@@ -103,17 +91,18 @@
beforeEach
(
function
()
{
state
=
jasmine
.
initializePlayer
();
speedControl
=
$
(
'div.speeds'
);
speedEntries
=
speedControl
.
children
(
'a'
);
spyOn
(
$
.
fn
,
'focus'
).
andCallThrough
();
speedControl
=
$
(
'.speeds'
);
speedButton
=
$
(
'.speed-button'
);
speedsContainer
=
$
(
'.video-speeds'
);
speedEntries
=
speedsContainer
.
find
(
'a'
);
});
it
(
'open/close the speed menu on mouseenter/mouseleave'
,
function
()
{
speedControl
.
mouseenter
();
expect
(
speedControl
).
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
toHaveClass
(
'
is-opened
'
);
speedControl
.
mouseleave
();
expect
(
speedControl
).
not
.
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
not
.
toHaveClass
(
'
is-opened
'
);
});
it
(
'do not close the speed menu on mouseleave if a speed '
+
...
...
@@ -121,64 +110,58 @@
// Open speed meenu. Focus is on last speed entry.
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
ENTER
));
speedControl
.
mouseenter
().
mouseleave
();
expect
(
speedControl
).
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
toHaveClass
(
'
is-opened
'
);
});
it
(
'close the speed menu on click'
,
function
()
{
speedControl
.
mouseenter
().
click
();
expect
(
speedControl
).
not
.
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
not
.
toHaveClass
(
'
is-opened
'
);
});
it
(
'close the speed menu on outside click'
,
function
()
{
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
ENTER
));
$
(
window
).
click
();
expect
(
speedControl
).
not
.
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
not
.
toHaveClass
(
'
is-opened
'
);
});
it
(
'open the speed menu on ENTER keydown'
,
function
()
{
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
ENTER
));
expect
(
speedControl
).
toHaveClass
(
'
open
'
);
expect
(
speedEntries
.
last
()
.
focus
).
toHaveBeenCall
ed
();
expect
(
speedControl
).
toHaveClass
(
'
is-opened
'
);
expect
(
speedEntries
.
last
()
).
toBeFocus
ed
();
});
it
(
'open the speed menu on SPACE keydown'
,
function
()
{
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
SPACE
));
expect
(
speedControl
).
toHaveClass
(
'
open
'
);
expect
(
speedEntries
.
last
()
.
focus
).
toHaveBeenCall
ed
();
expect
(
speedControl
).
toHaveClass
(
'
is-opened
'
);
expect
(
speedEntries
.
last
()
).
toBeFocus
ed
();
});
it
(
'open the speed menu on UP keydown'
,
function
()
{
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
expect
(
speedControl
).
toHaveClass
(
'
open
'
);
expect
(
speedEntries
.
last
()
.
focus
).
toHaveBeenCall
ed
();
expect
(
speedControl
).
toHaveClass
(
'
is-opened
'
);
expect
(
speedEntries
.
last
()
).
toBeFocus
ed
();
});
it
(
'close the speed menu on ESCAPE keydown'
,
function
()
{
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
ESCAPE
));
expect
(
speedControl
).
not
.
toHaveClass
(
'
open
'
);
expect
(
speedControl
).
not
.
toHaveClass
(
'
is-opened
'
);
});
it
(
'UP and DOWN keydown function as expected on speed entries'
,
function
()
{
// Iterate through list in both directions and check if
// things wrap up correctly.
var
lastEntry
=
speedEntries
.
length
-
1
,
i
;
var
lastEntry
=
speedEntries
.
length
-
1
,
speed_0_75
=
speedEntries
.
filter
(
':contains("0.75x")'
),
speed_1_0
=
speedEntries
.
filter
(
':contains("1.0x")'
)
;
// First open menu
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
expect
(
speed_0_75
).
toBeFocused
();
// Iterate with UP key until we have looped.
for
(
i
=
lastEntry
;
i
>=
0
;
i
--
)
{
speedEntries
.
eq
(
i
).
trigger
(
keyPressEvent
(
KEY
.
UP
));
}
// Iterate with DOWN key until we have looped.
for
(
i
=
0
;
i
<=
lastEntry
;
i
++
)
{
speedEntries
.
eq
(
i
).
trigger
(
keyPressEvent
(
KEY
.
DOWN
));
}
// Test if each element has been called twice.
expect
(
$
.
fn
.
focus
.
calls
.
length
)
.
toEqual
(
2
*
speedEntries
.
length
);
speed_0_75
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
expect
(
speed_1_0
).
toBeFocused
();
speed_1_0
.
trigger
(
keyPressEvent
(
KEY
.
DOWN
));
expect
(
speed_0_75
).
toBeFocused
();
});
it
(
'ESC keydown on speed entry closes menu'
,
function
()
{
...
...
@@ -188,8 +171,8 @@
// Menu is closed and focus has been returned to speed
// control.
expect
(
speedControl
).
not
.
toHaveClass
(
'
open
'
);
expect
(
speed
Control
.
focus
).
toHaveBeenCall
ed
();
expect
(
speedControl
).
not
.
toHaveClass
(
'
is-opened
'
);
expect
(
speed
Button
).
toBeFocus
ed
();
});
it
(
'ENTER keydown on speed entry selects speed and closes menu'
,
...
...
@@ -197,15 +180,15 @@
// First open menu.
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
// Focus on 1.50x speed
speedEntries
.
eq
(
1
).
focus
();
speedEntries
.
eq
(
1
).
trigger
(
keyPressEvent
(
KEY
.
ENTER
));
speedEntries
.
eq
(
0
).
focus
();
speedEntries
.
eq
(
0
).
trigger
(
keyPressEvent
(
KEY
.
ENTER
));
// Menu is closed, focus has been returned to speed
// control and video speed is 1.50x.
expect
(
speed
Control
.
focus
).
toHaveBeenCall
ed
();
expect
(
$
(
'.video
_
speeds li[data-speed="1.50"]'
))
.
toHaveClass
(
'active'
);
expect
(
$
(
'.speeds
p.activ
e'
)).
toHaveHtml
(
'1.50x'
);
expect
(
speed
Button
).
toBeFocus
ed
();
expect
(
$
(
'.video
-
speeds li[data-speed="1.50"]'
))
.
toHaveClass
(
'
is-
active'
);
expect
(
$
(
'.speeds
.valu
e'
)).
toHaveHtml
(
'1.50x'
);
});
it
(
'SPACE keydown on speed entry selects speed and closes menu'
,
...
...
@@ -213,39 +196,15 @@
// First open menu.
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
// Focus on 1.50x speed
speedEntries
.
eq
(
1
).
focus
();
speedEntries
.
eq
(
1
).
trigger
(
keyPressEvent
(
KEY
.
SPACE
));
speedEntries
.
eq
(
0
).
focus
();
speedEntries
.
eq
(
0
).
trigger
(
keyPressEvent
(
KEY
.
SPACE
));
// Menu is closed, focus has been returned to speed
// control and video speed is 1.50x.
expect
(
speedControl
.
focus
).
toHaveBeenCalled
();
expect
(
$
(
'.video_speeds li[data-speed="1.50"]'
))
.
toHaveClass
(
'active'
);
expect
(
$
(
'.speeds p.active'
)).
toHaveHtml
(
'1.50x'
);
});
it
(
'TAB + SHIFT keydown on speed entry closes menu and gives '
+
'focus to Play/Pause control'
,
function
()
{
// First open menu. Focus is on last speed entry.
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
speedEntries
.
last
().
trigger
(
tabBackPressEvent
());
// Menu is closed and focus has been given to Play/Pause
// control.
expect
(
state
.
videoControl
.
playPauseEl
.
focus
)
.
toHaveBeenCalled
();
});
it
(
'TAB keydown on speed entry closes menu and gives focus '
+
'to Volume control'
,
function
()
{
// First open menu. Focus is on last speed entry.
speedControl
.
trigger
(
keyPressEvent
(
KEY
.
UP
));
speedEntries
.
last
().
trigger
(
tabForwardPressEvent
());
// Menu is closed and focus has been given to Volume
// control.
expect
(
state
.
videoVolumeControl
.
buttonEl
.
focus
)
.
toHaveBeenCalled
();
expect
(
speedButton
).
toBeFocused
();
expect
(
$
(
'.video-speeds li[data-speed="1.50"]'
))
.
toHaveClass
(
'is-active'
);
expect
(
$
(
'.speeds .value'
)).
toHaveHtml
(
'1.50x'
);
});
});
});
...
...
@@ -261,14 +220,16 @@
beforeEach
(
function
()
{
state
=
jasmine
.
initializePlayer
();
state
.
videoSpeedControl
.
setSpeed
(
1.0
);
spyOn
(
state
.
videoPlayer
,
'onSpeedChange
'
).
andCallThrough
();
spyOn
(
$
.
fn
,
'trigger
'
).
andCallThrough
();
$
(
'li[data-speed="0.75"] a'
).
click
();
});
it
(
'trigger speedChange event'
,
function
()
{
expect
(
state
.
videoPlayer
.
onSpeedChange
).
toHaveBeenCalled
();
expect
(
state
.
videoSpeedControl
.
currentSpeed
).
toEqual
(
0.75
);
expect
(
$
.
fn
.
trigger
.
mostRecentCall
.
args
[
0
]).
toEqual
(
'speedchange'
,
[
'0.75'
]
);
expect
(
state
.
videoSpeedControl
.
currentSpeed
).
toEqual
(
'0.75'
);
});
});
});
...
...
@@ -276,16 +237,16 @@
describe
(
'onSpeedChange'
,
function
()
{
beforeEach
(
function
()
{
state
=
jasmine
.
initializePlayer
();
$
(
'li[data-speed="1.0"]
a'
).
addClass
(
'
active'
);
$
(
'li[data-speed="1.0"]
'
).
addClass
(
'is-
active'
);
state
.
videoSpeedControl
.
setSpeed
(
0.75
);
});
it
(
'set the new speed as active'
,
function
()
{
expect
(
$
(
'.video
_
speeds li[data-speed="1.0"]'
))
.
not
.
toHaveClass
(
'active'
);
expect
(
$
(
'.video
_
speeds li[data-speed="0.75"]'
))
.
toHaveClass
(
'active'
);
expect
(
$
(
'.speeds
p.activ
e'
)).
toHaveHtml
(
'0.75x'
);
expect
(
$
(
'.video
-
speeds li[data-speed="1.0"]'
))
.
not
.
toHaveClass
(
'
is-
active'
);
expect
(
$
(
'.video
-
speeds li[data-speed="0.75"]'
))
.
toHaveClass
(
'
is-
active'
);
expect
(
$
(
'.speeds
.valu
e'
)).
toHaveHtml
(
'0.75x'
);
});
});
});
...
...
common/lib/xmodule/xmodule/js/src/video/00_iterator.js
0 → 100644
View file @
24b631f7
(
function
(
define
)
{
define
(
'video/00_iterator.js'
,
[],
function
()
{
"use strict"
;
/**
* Provides convenient way to work with iterable data.
* @exports video/00_iterator.js
* @constructor
* @param {array} list Array to be iterated.
*/
var
Iterator
=
function
(
list
)
{
this
.
list
=
list
;
this
.
index
=
0
;
this
.
size
=
this
.
list
.
length
;
this
.
lastIndex
=
this
.
list
.
length
-
1
;
};
Iterator
.
prototype
=
{
/**
* Checks validity of provided index for the iterator.
* @access protected
* @param {numebr} index
* @return {boolean}
*/
_isValid
:
function
(
index
)
{
return
_
.
isNumber
(
index
)
&&
index
<
this
.
size
&&
index
>=
0
;
},
/**
* Returns next element.
* @param {number} [index] Updates current position.
* @return {any}
*/
next
:
function
(
index
)
{
if
(
!
(
this
.
_isValid
(
index
)))
{
index
=
this
.
index
;
}
this
.
index
=
(
index
>=
this
.
lastIndex
)
?
0
:
index
+
1
;
return
this
.
list
[
this
.
index
];
},
/**
* Returns previous element.
* @param {number} [index] Updates current position.
* @return {any}
*/
prev
:
function
(
index
)
{
if
(
!
(
this
.
_isValid
(
index
)))
{
index
=
this
.
index
;
}
this
.
index
=
(
index
<
1
)
?
this
.
lastIndex
:
index
-
1
;
return
this
.
list
[
this
.
index
];
},
/**
* Returns last element in the list.
* @return {any}
*/
last
:
function
()
{
return
this
.
list
[
this
.
lastIndex
];
},
/**
* Returns first element in the list.
* @return {any}
*/
first
:
function
()
{
return
this
.
list
[
0
];
},
/**
* Returns `true` if current position is last for the iterator.
* @return {boolean}
*/
isEnd
:
function
()
{
return
this
.
index
===
this
.
lastIndex
;
}
};
return
Iterator
;
});
}(
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
24b631f7
...
...
@@ -74,6 +74,7 @@ function (VideoPlayer, VideoStorage) {
saveState
:
saveState
,
setPlayerMode
:
setPlayerMode
,
setSpeed
:
setSpeed
,
speedToString
:
speedToString
,
trigger
:
trigger
,
youtubeId
:
youtubeId
},
...
...
@@ -519,9 +520,9 @@ function (VideoPlayer, VideoStorage) {
}
this
.
lang
=
this
.
config
.
transcriptLanguage
;
this
.
speed
=
Number
(
this
.
speed
=
this
.
speedToString
(
this
.
config
.
speed
||
this
.
config
.
generalSpeed
)
.
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
)
;
);
if
(
!
(
_parseYouTubeIDs
(
this
)))
{
...
...
@@ -630,7 +631,7 @@ function (VideoPlayer, VideoStorage) {
var
speed
;
video
=
video
.
split
(
/:/
);
speed
=
parseFloat
(
video
[
0
]).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
);
speed
=
_this
.
speedToString
(
video
[
0
]
);
_this
.
videos
[
speed
]
=
video
[
1
];
});
...
...
@@ -844,6 +845,10 @@ function (VideoPlayer, VideoStorage) {
return
this
.
getPlayerMode
()
===
'html5'
;
}
function
speedToString
(
speed
)
{
return
parseFloat
(
speed
).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
);
}
function
getCurrentLanguage
()
{
var
keys
=
_
.
keys
(
this
.
config
.
transcriptLanguages
);
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
24b631f7
...
...
@@ -167,6 +167,7 @@ function (HTML5Video, Resizer) {
dfd
.
resolve
();
}
}
function
_updateVcrAndRegion
(
state
,
isYoutube
)
{
var
update
=
function
(
state
)
{
var
duration
=
state
.
videoPlayer
.
duration
(),
...
...
@@ -384,8 +385,6 @@ function (HTML5Video, Resizer) {
this
.
setSpeed
(
newSpeed
,
true
);
this
.
videoPlayer
.
setPlaybackRate
(
newSpeed
);
this
.
el
.
trigger
(
'speedchange'
,
arguments
);
this
.
saveState
(
true
,
{
speed
:
newSpeed
});
}
...
...
@@ -522,6 +521,10 @@ function (HTML5Video, Resizer) {
dfd
.
resolve
();
this
.
el
.
on
(
'speedchange'
,
function
(
event
,
speed
)
{
_this
.
videoPlayer
.
onSpeedChange
(
speed
);
});
this
.
videoPlayer
.
log
(
'load_video'
);
availablePlaybackRates
=
this
.
videoPlayer
.
player
...
...
@@ -590,21 +593,14 @@ function (HTML5Video, Resizer) {
_this
.
speeds
.
push
(
key
);
});
this
.
trigger
(
'videoSpeedControl.reRender'
,
{
newSpeeds
:
this
.
speeds
,
currentSpeed
:
this
.
speed
}
);
this
.
setSpeed
(
this
.
speed
);
this
.
trigger
(
'videoSpeedControl.setSpeed'
,
this
.
speed
);
this
.
el
.
trigger
(
'speed:render'
,
[
this
.
speeds
,
this
.
speed
]
);
}
}
if
(
this
.
isFlashMode
())
{
this
.
setSpeed
(
this
.
speed
);
this
.
trigger
(
'videoSpeedControl.setSpeed'
,
this
.
speed
);
this
.
el
.
trigger
(
'speed:set'
,
[
this
.
speed
]
);
}
if
(
this
.
isHtml5Mode
())
{
...
...
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
View file @
24b631f7
...
...
@@ -62,8 +62,6 @@ function () {
previousVolume
=
100
,
slider
,
buttonStr
,
volumeSliderHandleEl
;
state
.
videoControl
.
secondaryControlsEl
.
prepend
(
element
);
if
(
!
isFinite
(
currentVolume
))
{
currentVolume
=
100
;
}
...
...
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
24b631f7
(
function
(
requirejs
,
require
,
define
)
{
// VideoSpeedControl module.
define
(
'video/08_video_speed_control.js'
,
[],
function
()
{
/
/ VideoSpeedControl() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
if
(
state
.
isTouch
)
{
// iOS doesn't support speed change
state
.
el
.
find
(
'div.speeds'
).
remove
();
dfd
.
resolve
();
return
dfd
.
promise
(
);
[
'video/00_iterator.js'
],
function
(
Iterator
)
{
"use strict"
;
/
**
* Video speed control module.
* @exports video/08_video_speed_control.js
* @constructor
* @param {object} state The object containing the state of the video player.
*/
var
SpeedControl
=
function
(
state
)
{
if
(
!
(
this
instanceof
SpeedControl
))
{
return
new
SpeedControl
(
state
);
}
state
.
videoSpeedControl
=
{};
this
.
state
=
state
;
this
.
state
.
videoSpeedControl
=
this
;
this
.
initialize
();
_initialize
(
state
);
dfd
.
resolve
()
;
return
$
.
Deferred
().
resolve
().
promise
(
);
}
;
if
(
state
.
videoType
===
'html5'
&&
!
(
_checkPlaybackRates
()))
{
console
.
log
(
'[Video info]: HTML5 mode - playbackRate is not supported.'
)
;
SpeedControl
.
prototype
=
{
/** Initializes the module. */
initialize
:
function
()
{
var
state
=
this
.
state
;
_hideSpeedControl
(
state
);
}
this
.
el
=
state
.
el
.
find
(
'.speeds'
);
this
.
speedsContainer
=
this
.
el
.
find
(
'.video-speeds'
);
this
.
speedButton
=
this
.
el
.
find
(
'.speed-button'
);
return
dfd
.
promise
();
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
function
_initialize
(
state
)
{
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
}
// 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
=
{
changeVideoSpeed
:
changeVideoSpeed
,
reRender
:
reRender
,
setSpeed
:
setSpeed
};
state
.
bindTo
(
methodsDict
,
state
.
videoSpeedControl
,
state
);
}
// 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
)
{
state
.
videoSpeedControl
.
speeds
=
state
.
speeds
;
state
.
videoSpeedControl
.
el
=
state
.
el
.
find
(
'div.speeds'
);
state
.
videoSpeedControl
.
videoSpeedsEl
=
state
.
videoSpeedControl
.
el
.
find
(
'.video_speeds'
);
state
.
videoControl
.
secondaryControlsEl
.
prepend
(
state
.
videoSpeedControl
.
el
);
$
.
each
(
state
.
videoSpeedControl
.
speeds
,
function
(
index
,
speed
)
{
var
link
=
'<a class="speed_link" href="#">'
+
speed
+
'x</a>'
;
state
.
videoSpeedControl
.
videoSpeedsEl
.
prepend
(
$
(
'<li data-speed="'
+
speed
+
'">'
+
link
+
'</li>'
)
if
(
!
this
.
isPlaybackRatesSupported
(
state
))
{
this
.
el
.
remove
();
console
.
log
(
'[Video info]: playbackRate is not supported.'
);
});
state
.
videoSpeedControl
.
setSpeed
(
state
.
speed
)
;
}
return
false
;
}
/**
* @desc Check if playbackRate supports by browser.
*
* @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 {Boolean}
* true: Browser support playbackRate functionality.
* false: Browser doesn't support playbackRate functionality.
*/
function
_checkPlaybackRates
()
{
var
video
=
document
.
createElement
(
'video'
);
// If browser supports, 1.0 should be returned by playbackRate
// property. In this case, function return True. Otherwise, False will
// be returned.
return
Boolean
(
video
.
playbackRate
);
}
// Hide speed control.
function
_hideSpeedControl
(
state
)
{
state
.
el
.
find
(
'div.speeds'
).
hide
();
}
// Get previous element in array or cyles back to the last if it is the
// first.
function
_previousSpeedLink
(
speedLinks
,
index
)
{
return
$
(
speedLinks
.
eq
(
index
<
1
?
speedLinks
.
length
-
1
:
index
-
1
));
}
// Get next element in array or cyles back to the first if it is the last.
function
_nextSpeedLink
(
speedLinks
,
index
)
{
return
$
(
speedLinks
.
eq
(
index
>=
speedLinks
.
length
-
1
?
0
:
index
+
1
));
}
function
_speedLinksFocused
(
state
)
{
var
speedLinks
=
state
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'a.speed_link'
);
return
speedLinks
.
is
(
':focus'
);
}
function
_openMenu
(
state
)
{
// When speed entries have focus, the menu stays open on
// mouseleave. A clickHandler is added to the window
// element to have clicks close the menu when they happen
// outside of it.
$
(
window
).
on
(
'click.speedMenu'
,
_clickHandler
.
bind
(
state
));
state
.
videoSpeedControl
.
el
.
addClass
(
'open'
);
}
function
_closeMenu
(
state
)
{
// Remove the previously added clickHandler from window element.
$
(
window
).
off
(
'click.speedMenu'
);
state
.
videoSpeedControl
.
el
.
removeClass
(
'open'
);
}
// Various event handlers. They all return false to stop propagation and
// prevent default behavior.
function
_clickHandler
(
event
)
{
var
target
=
$
(
event
.
currentTarget
);
this
.
videoSpeedControl
.
el
.
removeClass
(
'open'
);
if
(
target
.
is
(
'a.speed_link'
))
{
this
.
videoSpeedControl
.
changeVideoSpeed
.
call
(
this
,
event
);
}
this
.
render
(
state
.
speeds
,
state
.
speed
);
this
.
bindHandlers
();
return
true
;
},
/**
* Creates any necessary DOM elements, attach them, and set their,
* initial configuration.
* @param {array} speeds List of speeds available for the player.
* @param {string|number} currentSpeed Current speed for the player.
*/
render
:
function
(
speeds
,
currentSpeed
)
{
var
self
=
this
,
speedsContainer
=
this
.
speedsContainer
,
reversedSpeeds
=
speeds
.
concat
().
reverse
(),
speedsList
=
$
.
map
(
reversedSpeeds
,
function
(
speed
,
index
)
{
return
[
'<li data-speed="'
,
speed
,
'" role="presentation">'
,
'<a class="speed-link" href="#" role="menuitem" tabindex="-1">'
,
speed
,
'x'
,
'</a>'
,
'</li>'
].
join
(
''
);
});
speedsContainer
.
html
(
speedsList
.
join
(
''
));
this
.
speedLinks
=
new
Iterator
(
speedsContainer
.
find
(
'.speed-link'
));
this
.
setSpeed
(
currentSpeed
,
true
,
true
);
},
/**
* Bind any necessary function callbacks to DOM events (click,
* mousemove, etc.).
*/
bindHandlers
:
function
()
{
var
self
=
this
;
// Attach various events handlers to the speed menu button.
this
.
el
.
on
({
'mouseenter'
:
this
.
mouseEnterHandler
.
bind
(
this
),
'mouseleave'
:
this
.
mouseLeaveHandler
.
bind
(
this
),
'click'
:
this
.
clickMenuHandler
.
bind
(
this
),
'keydown'
:
this
.
keyDownMenuHandler
.
bind
(
this
)
});
// Attach click and keydown event handlers to the individual speed
// entries.
this
.
speedsContainer
.
on
({
click
:
this
.
clickLinkHandler
.
bind
(
this
),
keydown
:
this
.
keyDownLinkHandler
.
bind
(
this
)
},
'a.speed-link'
);
this
.
state
.
el
.
on
({
'speed:set'
:
function
(
event
,
speed
)
{
self
.
setSpeed
(
speed
,
true
);
},
'speed:render'
:
function
(
event
,
speeds
,
currentSpeed
)
{
self
.
render
(
speeds
,
currentSpeed
);
}
});
},
/**
* Check if playbackRate supports by browser. If browser supports, 1.0
* should be returned by playbackRate property. In this case, function
* return True. Otherwise, False will be returned.
* iOS doesn't support speed change.
* @param {object} state The object containing the state of the video
* player.
* @return {boolean}
* true: Browser support playbackRate functionality.
* false: Browser doesn't support playbackRate functionality.
*/
isPlaybackRatesSupported
:
function
(
state
)
{
var
isHtml5
=
state
.
videoType
===
'html5'
,
isTouch
=
state
.
isTouch
,
video
=
document
.
createElement
(
'video'
);
return
!
isTouch
||
(
isHtml5
&&
!
Boolean
(
video
.
playbackRate
));
},
/**
* Opens speed menu.
* @param {boolean} [bindEvent] Click event will be attached on window.
*/
openMenu
:
function
(
bindEvent
)
{
// When speed entries have focus, the menu stays open on
// mouseleave. A clickHandler is added to the window
// element to have clicks close the menu when they happen
// outside of it.
if
(
bindEvent
)
{
$
(
window
).
on
(
'click.speedMenu'
,
this
.
clickMenuHandler
.
bind
(
this
));
}
return
false
;
}
this
.
el
.
addClass
(
'is-opened'
);
this
.
speedButton
.
attr
(
'tabindex'
,
-
1
);
},
/**
* Closes speed menu.
* @param {boolean} [unBindEvent] Click event will be detached from window.
*/
closeMenu
:
function
(
unBindEvent
)
{
// Remove the previously added clickHandler from window element.
if
(
unBindEvent
)
{
$
(
window
).
off
(
'click.speedMenu'
);
}
// We do not use _openMenu and _closeMenu in the following two handlers
// because we do not want to add an unnecessary clickHandler to the window
// element.
function
_mouseEnterHandler
(
event
)
{
this
.
videoSpeedControl
.
el
.
addClass
(
'open'
);
this
.
el
.
removeClass
(
'is-opened'
);
this
.
speedButton
.
attr
(
'tabindex'
,
0
);
},
/**
* Sets new current speed for the speed control and triggers `speedchange`
* event if needed.
* @param {string|number} speed Speed to be set.
* @param {boolean} [silent] Sets the new speed without triggering
* `speedchange` event.
* @param {boolean} [forceUpdate] Updates the speed even if it's
* not differs from current speed.
*/
setSpeed
:
function
(
speed
,
silent
,
forceUpdate
)
{
if
(
speed
!==
this
.
currentSpeed
||
forceUpdate
)
{
this
.
speedsContainer
.
find
(
'li'
)
.
removeClass
(
'is-active'
)
.
siblings
(
"li[data-speed='"
+
speed
+
"']"
)
.
addClass
(
'is-active'
);
this
.
speedButton
.
find
(
'.value'
).
html
(
speed
+
'x'
);
this
.
currentSpeed
=
speed
;
if
(
!
silent
)
{
this
.
el
.
trigger
(
'speedchange'
,
[
speed
]);
}
}
},
return
false
;
}
/**
* Click event handler for the menu.
* @param {jquery Event} event
*/
clickMenuHandler
:
function
(
event
)
{
this
.
closeMenu
();
function
_mouseLeaveHandler
(
event
)
{
// Only close the menu is no speed entry has focus.
if
(
!
_speedLinksFocused
(
this
))
{
this
.
videoSpeedControl
.
el
.
removeClass
(
'open'
);
}
return
false
;
}
return
false
;
},
function
_keyDownHandler
(
event
)
{
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
,
target
=
$
(
event
.
currentTarget
),
speedButtonLink
=
this
.
videoSpeedControl
.
el
.
children
(
'a'
),
speedLinks
=
this
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'a.speed_link'
),
index
;
/**
* Click event handler for speed links.
* @param {jquery Event} event
*/
clickLinkHandler
:
function
(
event
)
{
var
speed
=
$
(
event
.
currentTarget
).
parent
().
data
(
'speed'
);
if
(
target
.
is
(
'a.speed_link'
))
{
this
.
closeMenu
();
this
.
setSpeed
(
this
.
state
.
speedToString
(
speed
));
index
=
target
.
parent
().
index
();
return
false
;
},
switch
(
keyCode
)
{
// Scroll up menu, wrapping at the top. Keep menu open.
case
KEY
.
UP
:
_previousSpeedLink
(
speedLinks
,
index
).
focus
();
break
;
// Scroll down menu, wrapping at the bottom. Keep menu
// open.
case
KEY
.
DOWN
:
_nextSpeedLink
(
speedLinks
,
index
).
focus
();
break
;
// Close menu.
case
KEY
.
TAB
:
_closeMenu
(
this
);
// Set focus to previous menu button in menu bar
// (Play/Pause button)
if
(
event
.
shiftKey
)
{
this
.
videoControl
.
playPauseEl
.
focus
();
}
// Set focus to next menu button in menu bar
// (Volume button)
else
{
this
.
videoVolumeControl
.
buttonEl
.
focus
();
}
break
;
// Close menu, give focus to speed control and change
// speed.
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
_closeMenu
(
this
);
speedButtonLink
.
focus
();
this
.
videoSpeedControl
.
changeVideoSpeed
.
call
(
this
,
event
);
break
;
// Close menu and give focus to speed control.
case
KEY
.
ESCAPE
:
_closeMenu
(
this
);
speedButtonLink
.
focus
();
break
;
/**
* Mouseenter event handler for the menu.
* @param {jquery Event} event
*/
mouseEnterHandler
:
function
(
event
)
{
this
.
openMenu
();
return
false
;
},
/**
* Mouseleave event handler for the menu.
* @param {jquery Event} event
*/
mouseLeaveHandler
:
function
(
event
)
{
// Only close the menu is no speed entry has focus.
if
(
!
this
.
speedLinks
.
list
.
is
(
':focus'
))
{
this
.
closeMenu
();
}
return
false
;
}
else
{
},
/**
* Keydown event handler for the menu.
* @param {jquery Event} event
*/
keyDownMenuHandler
:
function
(
event
)
{
var
KEY
=
$
.
ui
.
keyCode
,
keyCode
=
event
.
keyCode
;
switch
(
keyCode
)
{
// Open menu and focus on last element of list above it.
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
case
KEY
.
UP
:
_openMenu
(
this
);
speedLinks
.
last
().
focus
();
this
.
openMenu
(
true
);
this
.
speedLinks
.
last
().
focus
();
break
;
// Close menu.
case
KEY
.
ESCAPE
:
_closeMenu
(
this
);
this
.
closeMenu
(
true
);
break
;
}
// We do not stop propagation and default behavior on a TAB
// keypress.
return
event
.
keyCode
===
KEY
.
TAB
;
}
}
},
/**
* Keydown event handler for speed links.
* @param {jquery Event} event
*/
keyDownLinkHandler
:
function
(
event
)
{
// ALT key is used to change (alternate) the function of
// other pressed keys. In this, do nothing.
if
(
event
.
altKey
)
{
return
true
;
}
/**
* @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}
*/
function
_bindHandlers
(
state
)
{
var
speedButton
=
state
.
videoSpeedControl
.
el
,
videoSpeeds
=
state
.
videoSpeedControl
.
videoSpeedsEl
;
// Attach various events handlers to the speed menu button.
speedButton
.
on
({
'mouseenter'
:
_mouseEnterHandler
.
bind
(
state
),
'mouseleave'
:
_mouseLeaveHandler
.
bind
(
state
),
'click'
:
_clickHandler
.
bind
(
state
),
'keydown'
:
_keyDownHandler
.
bind
(
state
)
});
// Attach click and keydown event handlers to the individual speed
// entries.
videoSpeeds
.
on
(
'click'
,
'a.speed_link'
,
_clickHandler
.
bind
(
state
))
.
on
(
'keydown'
,
'a.speed_link'
,
_keyDownHandler
.
bind
(
state
));
}
// ***************************************************************
// 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
setSpeed
(
speed
)
{
this
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'li'
).
removeClass
(
'active'
);
this
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
"li[data-speed='"
+
speed
+
"']"
)
.
addClass
(
'active'
);
this
.
videoSpeedControl
.
el
.
find
(
'p.active'
).
html
(
''
+
speed
+
'x'
);
}
function
changeVideoSpeed
(
event
)
{
var
parentEl
=
$
(
event
.
target
).
parent
();
event
.
preventDefault
();
if
(
!
parentEl
.
hasClass
(
'active'
))
{
this
.
videoSpeedControl
.
currentSpeed
=
parentEl
.
data
(
'speed'
);
this
.
videoSpeedControl
.
setSpeed
(
// To meet the API expected format.
parseFloat
(
this
.
videoSpeedControl
.
currentSpeed
)
.
toFixed
(
2
)
.
replace
(
/
\.
00$/
,
'.0'
)
);
this
.
trigger
(
'videoPlayer.onSpeedChange'
,
this
.
videoSpeedControl
.
currentSpeed
);
}
}
var
KEY
=
$
.
ui
.
keyCode
,
self
=
this
,
parent
=
$
(
event
.
currentTarget
).
parent
(),
index
=
parent
.
index
(),
speed
=
parent
.
data
(
'speed'
);
function
reRender
(
params
)
{
var
_this
=
this
;
switch
(
event
.
keyCode
)
{
// Close menu.
case
KEY
.
TAB
:
// Closes menu after 25ms delay to change `tabindex` after
// finishing default behavior.
setTimeout
(
function
()
{
self
.
closeMenu
(
true
);
},
25
);
this
.
videoSpeedControl
.
videoSpeedsEl
.
empty
();
this
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'li'
).
removeClass
(
'active'
);
this
.
videoSpeedControl
.
speeds
=
params
.
newSpeeds
;
return
true
;
// Close menu and give focus to speed control.
case
KEY
.
ESCAPE
:
this
.
closeMenu
(
true
);
this
.
speedButton
.
focus
();
$
.
each
(
this
.
videoSpeedControl
.
speeds
,
function
(
index
,
speed
)
{
var
link
,
listItem
;
return
false
;
// Scroll up menu, wrapping at the top. Keep menu open.
case
KEY
.
UP
:
// Shift + Arrows keyboard shortcut might be used by
// screen readers. In this, do nothing.
if
(
event
.
shiftKey
)
{
return
true
;
}
link
=
'<a class="speed_link" href="#" role="menuitem">'
+
speed
+
'x</a>'
;
this
.
speedLinks
.
prev
(
index
).
focus
();
return
false
;
// Scroll down menu, wrapping at the bottom. Keep menu
// open.
case
KEY
.
DOWN
:
// Shift + Arrows keyboard shortcut might be used by
// screen readers. In this, do nothing.
if
(
event
.
shiftKey
)
{
return
true
;
}
listItem
=
$
(
'<li data-speed="'
+
speed
+
'" role="presentation">'
+
link
+
'</li>'
);
this
.
speedLinks
.
next
(
index
).
focus
();
return
false
;
// Close menu, give focus to speed control and change
// speed.
case
KEY
.
ENTER
:
case
KEY
.
SPACE
:
this
.
closeMenu
(
true
);
this
.
speedButton
.
focus
();
this
.
setSpeed
(
this
.
state
.
speedToString
(
speed
));
if
(
speed
===
params
.
currentSpeed
)
{
listItem
.
addClass
(
'active'
);
return
false
;
}
_this
.
videoSpeedControl
.
videoSpeedsEl
.
prepend
(
listItem
);
});
// Re-attach all events with their appropriate callbacks to the
// newly generated elements.
_bindHandlers
(
this
);
}
return
true
;
}
};
return
SpeedControl
;
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
View file @
24b631f7
...
...
@@ -138,7 +138,7 @@ function (Sjson, AsyncProcess) {
onContainerMouseEnter
:
function
(
event
)
{
event
.
preventDefault
();
$
(
event
.
currentTarget
).
addClass
(
'
open
'
);
$
(
event
.
currentTarget
).
addClass
(
'
is-opened
'
);
},
/**
...
...
@@ -149,7 +149,7 @@ function (Sjson, AsyncProcess) {
onContainerMouseLeave
:
function
(
event
)
{
event
.
preventDefault
();
$
(
event
.
currentTarget
).
removeClass
(
'
open
'
);
$
(
event
.
currentTarget
).
removeClass
(
'
is-opened
'
);
},
/**
...
...
@@ -364,7 +364,7 @@ function (Sjson, AsyncProcess) {
link
=
$
(
'<a href="javascript:void(0);">'
+
label
+
'</a>'
);
if
(
currentLang
===
code
)
{
li
.
addClass
(
'active'
);
li
.
addClass
(
'
is-
active'
);
}
li
.
append
(
link
);
...
...
@@ -381,9 +381,9 @@ function (Sjson, AsyncProcess) {
if
(
state
.
lang
!==
langCode
)
{
state
.
lang
=
langCode
;
state
.
storage
.
setItem
(
'language'
,
langCode
);
el
.
addClass
(
'active'
)
el
.
addClass
(
'
is-
active'
)
.
siblings
(
'li'
)
.
removeClass
(
'active'
);
.
removeClass
(
'
is-
active'
);
self
.
fetchCaption
();
}
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
24b631f7
...
...
@@ -71,6 +71,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
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_sjson.js'
),
resource_string
(
module
,
'js/src/video/00_iterator.js'
),
resource_string
(
module
,
'js/src/video/01_initialize.js'
),
resource_string
(
module
,
'js/src/video/025_focus_grabber.js'
),
resource_string
(
module
,
'js/src/video/02_html5_video.js'
),
...
...
lms/djangoapps/courseware/features/video.py
View file @
24b631f7
...
...
@@ -218,14 +218,14 @@ def navigate_to_an_item_in_a_sequence(number):
def
change_video_speed
(
speed
):
world
.
browser
.
execute_script
(
"$('.speeds').addClass('
open
')"
)
world
.
browser
.
execute_script
(
"$('.speeds').addClass('
is-opened
')"
)
speed_css
=
'li[data-speed="{0}"] a'
.
format
(
speed
)
world
.
wait_for_visible
(
'.speeds'
)
world
.
css_click
(
speed_css
)
def
open_menu
(
menu
):
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('
open
')"
.
format
(
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('
is-opened
')"
.
format
(
selector
=
VIDEO_MENUS
[
menu
]
))
...
...
@@ -379,7 +379,7 @@ def open_video(_step, player_id):
@step
(
'video "([^"]*)" should start playing at speed "([^"]*)"$'
)
def
check_video_speed
(
_step
,
player_id
,
speed
):
speed_css
=
'.speeds
p.activ
e'
speed_css
=
'.speeds
.valu
e'
assert
world
.
css_has_text
(
speed_css
,
'{0}x'
.
format
(
speed
))
...
...
@@ -397,7 +397,6 @@ def video_is_rendered(_step, mode):
}
html_tag
=
modes
[
mode
.
lower
()]
assert
world
.
css_find
(
'.video {0}'
.
format
(
html_tag
))
.
first
assert
world
.
is_css_present
(
'.speed_link'
)
@step
(
'videos have rendered in "([^"]*)" mode$'
)
...
...
@@ -412,7 +411,6 @@ def videos_are_rendered(_step, mode):
actual
=
len
(
world
.
css_find
(
'.video {0}'
.
format
(
html_tag
)))
expected
=
len
(
world
.
css_find
(
'.xmodule_VideoModule'
))
assert
actual
==
expected
assert
world
.
is_css_present
(
'.speed_link'
)
@step
(
'all sources are correct$'
)
...
...
@@ -494,8 +492,8 @@ def select_language(_step, code):
world
.
wait_for_present
(
'.lang.open'
)
world
.
css_click
(
selector
)
assert
world
.
css_has_class
(
selector
,
'active'
)
assert
len
(
world
.
css_find
(
VIDEO_MENUS
[
"language"
]
+
' li.active'
))
==
1
assert
world
.
css_has_class
(
selector
,
'
is-
active'
)
assert
len
(
world
.
css_find
(
VIDEO_MENUS
[
"language"
]
+
' li.
is-
active'
))
==
1
# Make sure that all ajax requests that affects the display of captions are finished.
# For example, request to get new translation etc.
...
...
lms/templates/video.html
View file @
24b631f7
...
...
@@ -72,11 +72,11 @@
</ul>
<div
class=
"secondary-controls"
>
<div
class=
"speeds menu-container"
>
<a
href=
"#"
title=
"${_('Speeds')}"
role=
"button"
aria-disabled=
"false"
>
<
h3>
${_('Speed')}
</h3
>
<
p
class=
"active"
></p
>
<a
class=
"speed-button"
href=
"#"
title=
"${_('Speeds')}"
role=
"button"
aria-disabled=
"false"
>
<
span
class=
"label"
>
${_('Speed')}
</span
>
<
span
class=
"value"
></span
>
</a>
<ol
class=
"video
_
speeds menu"
role=
"menu"
></ol>
<ol
class=
"video
-
speeds menu"
role=
"menu"
></ol>
</div>
<div
class=
"volume"
>
<a
href=
"#"
title=
"${_('Volume')}"
role=
"button"
aria-disabled=
"false"
></a>
...
...
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