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
f0492b3b
Commit
f0492b3b
authored
Apr 04, 2014
by
polesye
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor speed control.
parent
363c38a8
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
634 additions
and
461 deletions
+634
-461
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
+60
-85
common/lib/xmodule/xmodule/js/src/video/00_iterator.js
+101
-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
+302
-309
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
+7
-7
lms/templates/video.html
+4
-4
No files found.
cms/djangoapps/contentstore/features/video-editor.py
View file @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
(
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 previous 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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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,7 +66,7 @@
});
describe
(
'when running on non-touch based device'
,
function
()
{
var
speedControl
,
speedEntries
,
var
speedControl
,
speedEntries
,
speedButton
,
KEY
=
$
.
ui
.
keyCode
,
keyPressEvent
=
function
(
key
)
{
...
...
@@ -103,17 +101,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 +120,62 @@
// 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
();
// // Iterate with DOWN key until we have looped.
speed_1_0
.
trigger
(
keyPressEvent
(
KEY
.
DOWN
));
expect
(
speed_0_75
).
toBeFocused
();
});
it
(
'ESC keydown on speed entry closes menu'
,
function
()
{
...
...
@@ -188,8 +185,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 +194,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 +210,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 +234,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 +251,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 @
f0492b3b
(
function
(
define
)
{
define
(
'video/00_iterator.js'
,
[],
function
()
{
"use strict"
;
/**
* @desc Provides convenient way to work with iterate able data.
*
* @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
=
{
/**
* @desc Returns `true` if current index is valid for the iterator.
*
* @access protected
* @returns {boolean}
*
*/
_isValid
:
function
(
index
)
{
return
_
.
isNumber
(
index
)
&&
index
<
this
.
size
&&
index
>=
0
;
},
/**
* @desc Returns next element.
*
* @param {number} index If set, updates current index of iterator.
* @returns {DOM element}
*
*/
next
:
function
(
index
)
{
if
(
!
(
this
.
_isValid
(
index
)))
{
index
=
this
.
index
;
}
this
.
index
=
(
index
>=
this
.
lastIndex
)
?
0
:
index
+
1
;
return
this
.
list
[
this
.
index
];
},
/**
* @desc Returns previous element.
*
* @param {number} index If set, updates current index of iterator.
* @returns {DOM element}
*
*/
prev
:
function
(
index
)
{
if
(
!
(
this
.
_isValid
(
index
)))
{
index
=
this
.
index
;
}
this
.
index
=
(
index
<
1
)
?
this
.
lastIndex
:
index
-
1
;
return
this
.
list
[
this
.
index
];
},
/**
* @desc Returns last element in the list.
*
* @returns {DOM element}
*
*/
last
:
function
()
{
return
this
.
list
[
this
.
lastIndex
];
},
/**
* @desc Returns first element in the list.
*
* @returns {DOM element}
*
*/
first
:
function
()
{
return
this
.
list
[
0
];
},
/**
* @desc Returns `true` if current position is last for the iterator.
*
* @returns {boolean}
*
*/
isEnd
:
function
()
{
return
this
.
index
===
this
.
lastIndex
;
}
};
return
Iterator
;
});
}(
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
(
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"
;
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
=
{
/**
* @desc Initializes the module.
*
*/
initialize
:
function
()
{
var
state
=
this
.
state
;
this
.
el
=
state
.
el
.
find
(
'.speeds'
);
this
.
speedsContainer
=
this
.
el
.
find
(
'.video-speeds'
);
this
.
speedButton
=
this
.
el
.
find
(
'.speed-button'
);
if
(
!
this
.
isPlaybackRatesSupported
(
state
))
{
this
.
el
.
remove
();
console
.
log
(
'[Video info]: playbackRate is not supported.'
);
_hideSpeedControl
(
state
)
;
}
return
false
;
}
return
dfd
.
promise
();
};
this
.
render
(
state
.
speeds
,
state
.
speed
);
this
.
bindHandlers
();
return
true
;
},
/**
* @desc Create 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
);
},
/**
* @desc 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
);
}
});
},
/**
* @desc 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 {string} videoType Type of the video player
*
* @returns {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
));
},
/**
* @desc Opens speed menu.
*
* @param {boolean} bindEvent If set, attaches click event 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
));
}
// ***************************************************************
// 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>'
)
);
});
state
.
videoSpeedControl
.
setSpeed
(
state
.
speed
);
}
/**
* @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
.
el
.
addClass
(
'is-opened'
);
this
.
speedButton
.
attr
(
'tabindex'
,
-
1
);
},
/**
* @desc Closes speed menu.
*
* @param {boolean} unBindEvent If set, detaches click event from window.
*
*/
closeMenu
:
function
(
unBindEvent
)
{
// Remove the previously added clickHandler from window element.
if
(
unBindEvent
)
{
$
(
window
).
off
(
'click.speedMenu'
);
}
return
false
;
}
this
.
el
.
removeClass
(
'is-opened'
);
this
.
speedButton
.
attr
(
'tabindex'
,
0
);
},
/**
* @desc 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 If set, sets new speed without triggering
* `speedchange` event.
* @param {boolean} forceUpdate If set, 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
]);
}
}
},
// 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'
);
/**
* @desc Click event handler for the menu.
*
* @param {jquery Event} event
*
*/
clickMenuHandler
:
function
(
event
)
{
this
.
closeMenu
();
return
false
;
}
return
false
;
},
function
_mouseLeaveHandler
(
event
)
{
// Only close the menu is no speed entry has focu
s.
if
(
!
_speedLinksFocused
(
this
))
{
this
.
videoSpeedControl
.
el
.
removeClass
(
'open'
);
}
return
false
;
}
/**
* @desc Click event handler for speed link
s.
*
* @param {jquery Event} event
*
*/
clickLinkHandler
:
function
(
event
)
{
var
speed
=
$
(
event
.
currentTarget
).
parent
().
data
(
'speed'
);
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
;
this
.
closeMenu
();
this
.
setSpeed
(
this
.
state
.
speedToString
(
speed
));
if
(
target
.
is
(
'a.speed_link'
))
{
return
false
;
},
index
=
target
.
parent
().
index
();
/**
* @desc Mouseenter event handler for the menu.
*
* @param {jquery Event} event
*
*/
mouseEnterHandler
:
function
(
event
)
{
this
.
openMenu
();
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
;
return
false
;
},
/**
* @desc 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
{
},
/**
* @desc 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
;
}
}
/**
* @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
);
}
}
},
/**
* @desc 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
;
}
function
reRender
(
params
)
{
var
_this
=
this
;
var
KEY
=
$
.
ui
.
keyCode
,
self
=
this
,
parent
=
$
(
event
.
currentTarget
).
parent
(),
index
=
parent
.
index
(),
speed
=
parent
.
data
(
'speed'
);
this
.
videoSpeedControl
.
videoSpeedsEl
.
empty
();
this
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'li'
).
removeClass
(
'active'
);
this
.
videoSpeedControl
.
speeds
=
params
.
newSpeeds
;
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
);
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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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 @
f0492b3b
...
...
@@ -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,7 @@ 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'
)
assert
world
.
is_css_present
(
'.speed
-
link'
)
@step
(
'videos have rendered in "([^"]*)" mode$'
)
...
...
@@ -412,7 +412,7 @@ 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'
)
assert
world
.
is_css_present
(
'.speed
-
link'
)
@step
(
'all sources are correct$'
)
...
...
@@ -494,8 +494,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 @
f0492b3b
...
...
@@ -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