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
153bc25d
Commit
153bc25d
authored
Nov 06, 2013
by
polesye
Committed by
Valera Rozuvan
Nov 15, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
BLD-502: Add improvements to Video player.
parent
50250d82
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
526 additions
and
132 deletions
+526
-132
CHANGELOG.rst
+7
-0
cms/djangoapps/contentstore/features/video.feature
+6
-6
cms/djangoapps/contentstore/features/video.py
+26
-21
cms/djangoapps/contentstore/views/item.py
+1
-0
common/lib/xmodule/xmodule/css/video/display.scss
+60
-48
common/lib/xmodule/xmodule/js/spec/video/resizer_spec.js
+79
-0
common/lib/xmodule/xmodule/js/src/video/00_resizer.js
+64
-9
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+47
-5
common/lib/xmodule/xmodule/js/src/video/025_focus_grabber.js
+5
-0
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+34
-22
common/lib/xmodule/xmodule/js/src/video/04_video_control.js
+5
-0
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
+5
-0
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
+5
-1
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
+5
-0
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+5
-8
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
+6
-1
common/lib/xmodule/xmodule/js/src/video/10_main.js
+10
-8
common/lib/xmodule/xmodule/static_content.py
+1
-0
common/static/sass/assets/_anims.scss
+154
-0
lms/templates/video.html
+1
-3
No files found.
CHANGELOG.rst
View file @
153bc25d
...
...
@@ -5,6 +5,13 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Blades: Video player:
- Add spinner;
- Improve initialization of modules;
- Speed up video resizing during page loading;
- Speed up acceptance tests. (BLD-502)
- Fix transcripts bug - when show_captions is set to false. BLD-467.
Studio: change create_item, delete_item, and save_item to RESTful API (STUD-847).
Blades: Fix answer choices rearranging if user tries to stylize something in the
...
...
cms/djangoapps/contentstore/features/video.feature
View file @
153bc25d
...
...
@@ -46,11 +46,11 @@ Feature: CMS.Video Component
Scenario
:
Closed captions become visible when the mouse hovers over CC button
Given
I have created a Video component with subtitles
And
Make sure captions are closed
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
And
I hover over button
"CC"
Then
Captions become
"visible"
And
I hover over button
"volume"
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
# 8
Scenario
:
Open captions never become invisible
...
...
@@ -66,7 +66,7 @@ Feature: CMS.Video Component
Scenario
:
Closed captions are invisible when mouse doesn't hover on CC button
Given
I have created a Video component with subtitles
And
Make sure captions are closed
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
And
I hover over button
"volume"
Then
Captions are
"invisible"
...
...
@@ -74,9 +74,9 @@ Feature: CMS.Video Component
Scenario
:
When
enter key is pressed on a caption shows an outline around it
Given
I have created a Video component with subtitles
And
Make sure captions are opened
Then
I focus on caption line with data-index
0
Then
I press
"enter"
button on caption line with data-index
0
And
I see caption line with data-index
0
has class
"focused"
Then
I focus on caption line with data-index
"0"
Then
I press
"enter"
button on caption line with data-index
"0"
And
I see caption line with data-index
"0"
has class
"focused"
# 11
# Disabled until we come up with a more solid test, as this one is brittle.
...
...
cms/djangoapps/contentstore/features/video.py
View file @
153bc25d
...
...
@@ -11,6 +11,11 @@ VIDEO_BUTTONS = {
'Play'
:
'.video_control.play'
,
}
SELECTORS
=
{
'spinner'
:
'.video-wrapper .spinner'
,
'controls'
:
'section.video-controls'
,
}
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY
=
0.5
...
...
@@ -23,6 +28,13 @@ def i_created_a_video_component(step):
category
=
'video'
,
)
world
.
wait_for_xmodule
()
world
.
disable_jquery_animations
()
world
.
wait_for_present
(
'.is-initialized'
)
world
.
wait
(
DELAY
)
assert
not
world
.
css_visible
(
SELECTORS
[
'spinner'
])
@step
(
'I have created a Video component with subtitles$'
)
def
i_created_a_video_with_subs
(
_step
):
...
...
@@ -41,7 +53,13 @@ def i_created_a_video_with_subs_with_name(_step, sub_id):
# Return to the video
world
.
visit
(
video_url
)
world
.
wait_for_xmodule
()
world
.
disable_jquery_animations
()
world
.
wait_for_present
(
'.is-initialized'
)
world
.
wait
(
DELAY
)
assert
not
world
.
css_visible
(
SELECTORS
[
'spinner'
])
@step
(
'I have uploaded subtitles "([^"]*)"$'
)
...
...
@@ -52,7 +70,6 @@ def i_have_uploaded_subtitles(_step, sub_id):
@step
(
'when I view the (.*) it does not have autoplay enabled$'
)
def
does_not_autoplay
(
_step
,
video_type
):
world
.
wait_for_xmodule
()
assert
world
.
css_find
(
'.
%
s'
%
video_type
)[
0
][
'data-autoplay'
]
==
'False'
assert
world
.
css_has_class
(
'.video_control'
,
'play'
)
...
...
@@ -73,7 +90,6 @@ def i_edit_the_component(_step):
@step
(
'I have (hidden|toggled) captions$'
)
def
hide_or_show_captions
(
step
,
shown
):
world
.
wait_for_xmodule
()
button_css
=
'a.hide-subtitles'
if
shown
==
'hidden'
:
world
.
css_click
(
button_css
)
...
...
@@ -118,18 +134,18 @@ def xml_only_video(step):
@step
(
'The correct Youtube video is shown$'
)
def
the_youtube_video_is_shown
(
_step
):
world
.
wait_for_xmodule
()
ele
=
world
.
css_find
(
'.video'
)
.
first
assert
ele
[
'data-streams'
]
.
split
(
':'
)[
1
]
==
world
.
scenario_dict
[
'YOUTUBE_ID'
]
@step
(
'Make sure captions are (.+)$'
)
def
set_captions_visibility_state
(
_step
,
captions_state
):
SELECTOR
=
'.closed .subtitles'
if
captions_state
==
'closed'
:
if
world
.
css_visible
(
'.subtitles'
):
if
not
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
else
:
if
not
world
.
css_visible
(
'.subtitles'
):
if
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
...
...
@@ -139,18 +155,7 @@ def hover_over_button(_step, button):
@step
(
'Captions (?:are|become) "([^"]*)"$'
)
def
are_captions_visibile
(
_step
,
visibility_state
):
_step
.
given
(
'Captions become "{0}" after 0 seconds'
.
format
(
visibility_state
))
@step
(
'Captions (?:are|become) "([^"]*)" after (.+) seconds$'
)
def
check_captions_visibility_state
(
_step
,
visibility_state
,
timeout
):
timeout
=
int
(
timeout
.
strip
())
# Captions become invisible by fading out. We must wait by a specified
# time.
world
.
wait
(
timeout
)
def
check_captions_visibility_state
(
_step
,
visibility_state
):
if
visibility_state
==
'visible'
:
assert
world
.
css_visible
(
'.subtitles'
)
else
:
...
...
@@ -162,17 +167,17 @@ def find_caption_line_by_data_index(index):
return
world
.
css_find
(
SELECTOR
)
.
first
@step
(
'I focus on caption line with data-index
(
\
d+)
$'
)
@step
(
'I focus on caption line with data-index
"([^"]*)"
$'
)
def
focus_on_caption_line
(
_step
,
index
):
find_caption_line_by_data_index
(
int
(
index
.
strip
()))
.
_element
.
send_keys
(
Keys
.
TAB
)
@step
(
'I press "enter" button on caption line with data-index
(
\
d+)
$'
)
def
focus_on_caption_line
(
_step
,
index
):
@step
(
'I press "enter" button on caption line with data-index
"([^"]*)"
$'
)
def
click_on_the_caption
(
_step
,
index
):
find_caption_line_by_data_index
(
int
(
index
.
strip
()))
.
_element
.
send_keys
(
Keys
.
ENTER
)
@step
(
'I see caption line with data-index
(
\
d+)
has class "([^"]*)"$'
)
@step
(
'I see caption line with data-index
"([^"]*)"
has class "([^"]*)"$'
)
def
caption_line_has_class
(
_step
,
index
,
className
):
SELECTOR
=
".subtitles > li[data-index='{index}']"
.
format
(
index
=
int
(
index
.
strip
()))
world
.
css_has_class
(
SELECTOR
,
className
.
strip
())
...
...
cms/djangoapps/contentstore/views/item.py
View file @
153bc25d
...
...
@@ -2,6 +2,7 @@
import
logging
from
uuid
import
uuid4
from
static_replace
import
replace_static_urls
from
django.core.exceptions
import
PermissionDenied
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
153bc25d
...
...
@@ -15,9 +15,17 @@ div.video {
border
:
0
;
}
&
.is-initialized
{
article
.video-wrapper
{
.spinner
{
display
:
none
;
}
}
}
div
.tc-wrapper
{
position
:
relative
;
@include
clearfix
;
position
:
relative
;
}
div
.focus_grabber
{
...
...
@@ -58,21 +66,38 @@ div.video {
float
:
left
;
margin-right
:
flex-gutter
(
9
);
width
:
flex-grid
(
6
,
9
);
background-color
:
black
;
position
:
relative
;
div
.video-player-pre
{
div
.video-player-pre
,
div
.video-player-post
{
height
:
50px
;
background-color
:
black
;
}
div
.video-player-post
{
height
:
50px
;
background-color
:
black
;
.spinner
{
@include
transform
(
translate
(
-50%
,
-50%
));
position
:
absolute
;
z-index
:
1
;
background
:
rgba
(
0
,
0
,
0
,
0
.7
);
top
:
50%
;
left
:
50%
;
padding
:
30px
;
border-radius
:
25%
;
&
:after
{
@include
animation
(
rotateCW
3s
infinite
linear
);
content
:
''
;
display
:
block
;
width
:
30px
;
height
:
30px
;
border
:
7px
solid
white
;
border-top-color
:
transparent
;
border-radius
:
100%
;
position
:
relative
;
}
}
section
.video-player
{
overflow
:
hidden
;
min-height
:
300px
;
...
...
@@ -85,7 +110,6 @@ div.video {
object
,
iframe
,
video
{
border
:
none
;
height
:
100%
;
width
:
100%
;
}
...
...
@@ -109,12 +133,13 @@ div.video {
&
:hover
{
ul
,
div
{
opacity
:
1
.0
;
opacity
:
1
;
}
}
div
.slider
{
@include
clearfix
();
@include
transform
(
scaleY
(
0
.5
)
translate3d
(
0
,
50%
,
0
));
background
:
#c2c2c2
;
border
:
1px
solid
#000
;
border-radius
:
0
;
...
...
@@ -132,7 +157,6 @@ div.video {
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
transition
:
transform
0
.7s
ease-in-out
;
@include
transform
(
scaleY
(
0
.5
)
translate3d
(
0
,
50%
,
0
));
div
.ui-widget-header
{
background
:
#777
;
...
...
@@ -141,23 +165,11 @@ div.video {
div
.ui-corner-all.slider-range
{
background-color
:
#1e91d3
;
/* We add opacity so that we can discern the amount of video that has
* been played. The progress will advance as a gray-shaded area. When
* it will overlap with the range, you will see a different shade of
* the range for part that has been played, and for part that is
* still to be played.
*
* For CSS opacity, different browsers are handled differently.
*/
-ms-filter
:
"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"
;
filter
:
alpha
(
opacity
=
30
);
-moz-opacity
:
0
.3
;
-khtml-opacity
:
0
.3
;
opacity
:
0
.3
;
}
a
.ui-slider-handle
{
@include
transform
(
scale
(
.7
,
1
.3
)
translate3d
(
-80%
,
-15%
,
0
));
background
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
background-size
:
50%
;
border
:
1px
solid
darken
(
$pink
,
20%
);
...
...
@@ -171,7 +183,6 @@ div.video {
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
transition
:
transform
0
.7s
ease-in-out
;
@include
transform
(
scale
(
.7
,
1
.3
)
translate3d
(
-80%
,
-15%
,
0
));
width
:
20px
;
&
:focus
,
&
:hover
{
...
...
@@ -268,25 +279,26 @@ div.video {
position
:
relative
;
&
.open
{
&
>
a
{
&
>
a
{
background
:
url('../images/open-arrow.png')
10px
center
no-repeat
;
}
ol
.video_speeds
{
display
:
block
;
opacity
:
1
.0
;
opacity
:
1
;
padding
:
0
;
margin
:
0
;
list-style
:
none
;
}
}
&
>
a
{
&
>
a
{
@include
clearfix
();
@include
transition
(
none
);
background
:
url('../images/closed-arrow.png')
10px
center
no-repeat
;
border-left
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
@include
clearfix
();
color
:
#fff
;
cursor
:
pointer
;
display
:
block
;
...
...
@@ -294,7 +306,6 @@ div.video {
margin-right
:
0
;
padding-left
:
15px
;
position
:
relative
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
width
:
116px
;
...
...
@@ -312,12 +323,12 @@ div.video {
&
:hover
{
outline
:
0
;
opacity
:
1
.0
;
opacity
:
1
;
background-color
:
#444
;
}
&
:active
{
opacity
:
1
.0
;
opacity
:
1
;
background-color
:
#444
;
}
...
...
@@ -349,13 +360,13 @@ div.video {
// fix for now
ol
.video_speeds
{
box-shadow
:
inset
1px
0
0
#555
,
0
4px
0
#444
;
@include
transition
(
none
);
box-shadow
:
inset
1px
0
0
#555
,
0
4px
0
#444
;
background-color
:
#444
;
border
:
1px
solid
#000
;
bottom
:
46px
;
display
:
none
;
opacity
:
0
.0
;
opacity
:
0
;
position
:
absolute
;
width
:
131px
;
...
...
@@ -404,24 +415,24 @@ div.video {
&
.open
{
.volume-slider-container
{
display
:
block
;
opacity
:
1
.0
;
opacity
:
1
;
}
}
&
.muted
{
&
>
a
{
&
>
a
{
background-image
:
url('../images/mute.png')
;
}
}
>
a
{
&
>
a
{
@include
clearfix
();
@include
transition
(
none
);
background-image
:
url('../images/volume.png')
;
background-position
:
10px
center
;
background-repeat
:
no-repeat
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
@include
clearfix
();
color
:
#fff
;
cursor
:
pointer
;
display
:
block
;
...
...
@@ -429,7 +440,6 @@ div.video {
margin-right
:
0
;
padding-left
:
15px
;
position
:
relative
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
...
...
@@ -442,13 +452,13 @@ div.video {
}
.volume-slider-container
{
box-shadow
:
inset
1px
0
0
#555
,
0
3px
0
#444
;
@include
transition
(
none
);
box-shadow
:
inset
1px
0
0
#555
,
0
3px
0
#444
;
background-color
:
#444
;
border
:
1px
solid
#000
;
bottom
:
46px
;
display
:
none
;
opacity
:
0
.0
;
opacity
:
0
;
position
:
absolute
;
width
:
45px
;
height
:
125px
;
...
...
@@ -465,6 +475,7 @@ div.video {
box-shadow
:
0
1px
0
#333
;
a
.ui-slider-handle
{
@include
transition
(
height
2
.0s
ease-in-out
0s
,
width
2
.0s
ease-in-out
0s
);
background
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
background-size
:
50%
;
border
:
1px
solid
darken
(
$pink
,
20%
);
...
...
@@ -473,7 +484,6 @@ div.video {
cursor
:
pointer
;
height
:
15px
;
left
:
-6px
;
@include
transition
(
height
2
.0s
ease-in-out
0s
,
width
2
.0s
ease-in-out
0s
);
width
:
15px
;
}
...
...
@@ -485,6 +495,7 @@ div.video {
}
a
.add-fullscreen
{
@include
transition
(
none
);
background
:
url(../images/fullscreen.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
...
...
@@ -495,7 +506,6 @@ div.video {
margin-left
:
0
;
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
@include
transition
(
none
);
width
:
30px
;
&
:hover
,
&
:active
{
...
...
@@ -508,6 +518,7 @@ div.video {
a
.quality_control
{
@include
transition
(
none
);
background
:
url(../images/hd.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
...
...
@@ -518,7 +529,6 @@ div.video {
margin-left
:
0
;
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
@include
transition
(
none
);
width
:
30px
;
&
:hover
{
...
...
@@ -538,16 +548,16 @@ div.video {
a
.hide-subtitles
{
@include
transition
(
none
);
background
:
url('../images/cc.png')
center
no-repeat
;
float
:
left
;
font-weight
:
800
;
line-height
:
46px
;
//height of play pause buttons
margin-left
:
0
;
opacity
:
1
.0
;
opacity
:
1
;
padding
:
0
lh
(
.5
);
position
:
relative
;
text-indent
:
-9999px
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
...
...
@@ -569,7 +579,7 @@ div.video {
&
:hover
section
.video-controls
{
ul
,
div
{
opacity
:
1
.0
;
opacity
:
1
;
}
div
.slider
{
...
...
@@ -732,6 +742,7 @@ div.video {
}
ol
.subtitles
{
@include
transition
(
none
);
background
:
rgba
(
#000
,
.8
);
bottom
:
0
;
height
:
100%
;
...
...
@@ -742,7 +753,6 @@ div.video {
right
:
0
;
top
:
0
;
visibility
:
visible
;
@include
transition
(
none
);
li
{
color
:
#aaa
;
...
...
@@ -754,3 +764,5 @@ div.video {
}
}
}
common/lib/xmodule/xmodule/js/spec/video/resizer_spec.js
View file @
153bc25d
...
...
@@ -97,6 +97,85 @@ function (Resizer) {
expect
(
realWidth
).
toBe
(
expectedWidth
);
});
describe
(
'Callbacks'
,
function
()
{
var
resizer
,
spiesList
=
[];
beforeEach
(
function
()
{
var
spiesCount
=
_
.
range
(
3
);
spiesList
=
$
.
map
(
spiesCount
,
function
()
{
return
jasmine
.
createSpy
();
});
resizer
=
new
Resizer
(
config
);
});
it
(
'callbacks are called'
,
function
()
{
$
.
each
(
spiesList
,
function
(
index
,
spy
)
{
resizer
.
callbacks
.
add
(
spy
);
});
resizer
.
align
();
$
.
each
(
spiesList
,
function
(
index
,
spy
)
{
expect
(
spy
).
toHaveBeenCalled
();
});
});
it
(
'callback called just once'
,
function
()
{
resizer
.
callbacks
.
once
(
spiesList
[
0
]);
resizer
.
align
()
.
alignByHeightOnly
();
expect
(
spiesList
[
0
].
calls
.
length
).
toEqual
(
1
);
});
it
(
'All callbacks are removed'
,
function
()
{
$
.
each
(
spiesList
,
function
(
index
,
spy
)
{
resizer
.
callbacks
.
add
(
spy
);
});
resizer
.
callbacks
.
removeAll
();
resizer
.
align
();
$
.
each
(
spiesList
,
function
(
index
,
spy
)
{
expect
(
spy
).
not
.
toHaveBeenCalled
();
});
});
it
(
'Specific callback is removed'
,
function
()
{
$
.
each
(
spiesList
,
function
(
index
,
spy
)
{
resizer
.
callbacks
.
add
(
spy
);
});
resizer
.
callbacks
.
remove
(
spiesList
[
1
]);
resizer
.
align
();
expect
(
spiesList
[
1
]).
not
.
toHaveBeenCalled
();
});
it
(
'Error message is shown when wrong argument type is passed'
,
function
()
{
var
methods
=
[
'add'
,
'once'
],
errorMessage
=
'TypeError: Argument is not a function.'
,
arg
=
{};
spyOn
(
console
,
'error'
);
$
.
each
(
methods
,
function
(
index
,
methodName
)
{
resizer
.
callbacks
[
methodName
](
arg
);
expect
(
console
.
error
).
toHaveBeenCalledWith
(
errorMessage
);
//reset spy
console
.
log
.
reset
();
});
});
});
});
});
...
...
common/lib/xmodule/xmodule/js/src/video/00_resizer.js
View file @
153bc25d
...
...
@@ -12,6 +12,8 @@ function () {
containerRatio
:
null
,
elementRatio
:
null
},
callbacksList
=
[],
module
=
{},
mode
=
null
,
config
;
...
...
@@ -28,7 +30,7 @@ function () {
);
}
return
this
;
return
module
;
};
var
getData
=
function
()
{
...
...
@@ -79,7 +81,9 @@ function () {
break
;
}
return
this
;
fireCallbacks
();
return
module
;
};
var
alignByWidthOnly
=
function
()
{
...
...
@@ -93,7 +97,7 @@ function () {
'left'
:
0
});
return
this
;
return
module
;
};
var
alignByHeightOnly
=
function
()
{
...
...
@@ -107,7 +111,7 @@ function () {
'left'
:
0.5
*
(
data
.
containerWidth
-
width
)
});
return
this
;
return
module
;
};
var
setMode
=
function
(
param
)
{
...
...
@@ -116,18 +120,69 @@ function () {
align
();
}
return
this
;
return
module
;
};
initialize
.
apply
(
this
,
arguments
);
var
addCallback
=
function
(
func
)
{
if
(
$
.
isFunction
(
func
))
{
callbacksList
.
push
(
func
);
}
else
{
console
.
error
(
'TypeError: Argument is not a function.'
);
}
return
{
return
module
;
};
var
addOnceCallback
=
function
(
func
)
{
if
(
$
.
isFunction
(
func
))
{
var
decorator
=
function
()
{
func
();
removeCallback
(
func
);
};
addCallback
(
decorator
);
}
else
{
console
.
error
(
'TypeError: Argument is not a function.'
);
}
return
module
;
};
var
fireCallbacks
=
function
()
{
$
.
each
(
callbacksList
,
function
(
index
,
callback
)
{
callback
();
});
};
var
removeCallbacks
=
function
()
{
callbacksList
.
length
=
0
;
return
module
;
};
var
removeCallback
=
function
(
func
)
{
var
index
=
$
.
inArray
(
func
,
callbacksList
);
if
(
index
!==
-
1
)
{
return
callbacksList
.
splice
(
index
,
1
);
}
};
initialize
.
apply
(
module
,
arguments
);
return
$
.
extend
(
true
,
module
,
{
align
:
align
,
alignByWidthOnly
:
alignByWidthOnly
,
alignByHeightOnly
:
alignByHeightOnly
,
setParams
:
initialize
,
setMode
:
setMode
};
setMode
:
setMode
,
callbacks
:
{
add
:
addCallback
,
once
:
addOnceCallback
,
remove
:
removeCallback
,
removeAll
:
removeCallbacks
}
});
};
return
Resizer
;
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
153bc25d
...
...
@@ -16,7 +16,6 @@ define(
'video/01_initialize.js'
,
[
'video/03_video_player.js'
],
function
(
VideoPlayer
)
{
// window.console.log() is expected to be available. We do not support
// browsers which lack this functionality.
...
...
@@ -42,7 +41,20 @@ function (VideoPlayer) {
*/
return
function
(
state
,
element
)
{
_makeFunctionsPublic
(
state
);
state
.
initialize
(
element
);
state
.
initialize
(
element
)
.
done
(
function
()
{
_initializeModules
(
state
)
.
done
(
function
()
{
state
.
el
.
addClass
(
'is-initialized'
)
.
find
(
'.spinner'
)
.
attr
({
'aria-hidden'
:
'true'
,
'tabindex'
:
-
1
});
});
});
};
// ***************************************************************
...
...
@@ -94,12 +106,20 @@ function (VideoPlayer) {
// Require JS. At the time when we reach this code, the stand alone
// HTML5 player is already loaded, so no further testing in that case
// is required.
var
video
;
if
(
state
.
videoType
===
'youtube'
)
{
YT
.
ready
(
function
()
{
VideoPlayer
(
state
);
video
=
VideoPlayer
(
state
);
state
.
modules
.
push
(
video
);
state
.
__dfd__
.
resolve
();
});
}
else
{
VideoPlayer
(
state
);
video
=
VideoPlayer
(
state
);
state
.
modules
.
push
(
video
);
state
.
__dfd__
.
resolve
();
}
}
...
...
@@ -191,6 +211,8 @@ function (VideoPlayer) {
state
.
html5Sources
.
mp4
===
null
&&
state
.
html5Sources
.
ogg
===
null
)
{
// TODO: use 1 class to work with.
state
.
el
.
find
(
'.video-player div'
).
addClass
(
'hidden'
);
state
.
el
.
find
(
'.video-player h3'
).
removeClass
(
'hidden'
);
...
...
@@ -224,6 +246,22 @@ function (VideoPlayer) {
state
.
captionHideTimeout
=
null
;
}
function
_initializeModules
(
state
)
{
var
dfd
=
$
.
Deferred
(),
modulesList
=
$
.
map
(
state
.
modules
,
function
(
module
)
{
if
(
$
.
isFunction
(
module
))
{
return
module
(
state
);
}
else
if
(
$
.
isPlainObject
(
module
))
{
return
module
;
}
});
$
.
when
.
apply
(
null
,
modulesList
)
.
done
(
dfd
.
resolve
);
return
dfd
.
promise
();
}
// ***************************************************************
// Public functions start here.
// These are available via the 'state' object. Their context ('this'
...
...
@@ -259,6 +297,7 @@ function (VideoPlayer) {
data
,
tempYtTestTimeout
;
// This is used in places where we instead would have to check if an
// element has a CSS class 'fullscreen'.
this
.
__dfd__
=
$
.
Deferred
();
this
.
isFullScreen
=
false
;
// The parent element of the video, and the ID.
...
...
@@ -313,8 +352,9 @@ function (VideoPlayer) {
// If we do not have YouTube ID's, try parsing HTML5 video sources.
if
(
!
_prepareHTML5Video
(
this
))
{
this
.
__dfd__
.
reject
();
// Non-YouTube sources were not found either.
return
;
return
this
.
__dfd__
.
promise
()
;
}
console
.
log
(
'[Video info]: Start player in HTML5 mode.'
);
...
...
@@ -381,6 +421,8 @@ function (VideoPlayer) {
_renderElements
(
_this
);
});
}
return
this
.
__dfd__
.
promise
();
}
/*
...
...
common/lib/xmodule/xmodule/js/src/video/025_focus_grabber.js
View file @
153bc25d
...
...
@@ -33,11 +33,16 @@ define(
[],
function
()
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
focusGrabber
=
{};
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
153bc25d
...
...
@@ -5,14 +5,18 @@ define(
'video/03_video_player.js'
,
[
'video/02_html5_video.js'
,
'video/00_resizer.js'
],
function
(
HTML5Video
,
Resizer
)
{
var
dfd
=
$
.
Deferred
();
// VideoPlayer() function - what this module "exports".
return
function
(
state
)
{
state
.
videoPlayer
=
{};
_makeFunctionsPublic
(
state
);
_initialize
(
state
);
// No callbacks to DOM events (click, mousemove, etc.).
return
dfd
.
promise
();
};
// ***************************************************************
...
...
@@ -56,7 +60,7 @@ function (HTML5Video, Resizer) {
// via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
function
_initialize
(
state
)
{
var
youTubeId
;
var
youTubeId
,
player
,
videoWidth
,
videoHeight
;
// The function is called just once to apply pre-defined configurations
// by student before video starts playing. Waits until the video's
...
...
@@ -138,9 +142,28 @@ function (HTML5Video, Resizer) {
.
onPlaybackQualityChange
}
});
player
=
state
.
videoEl
=
state
.
el
.
find
(
'iframe'
);
videoWidth
=
player
.
attr
(
'width'
)
||
player
.
width
();
videoHeight
=
player
.
attr
(
'height'
)
||
player
.
height
();
_resize
(
state
,
videoWidth
,
videoHeight
);
}
}
function
_resize
(
state
,
videoWidth
,
videoHeight
)
{
state
.
resizer
=
new
Resizer
({
element
:
state
.
videoEl
,
elementRatio
:
videoWidth
/
videoHeight
,
container
:
state
.
videoEl
.
parent
()
})
.
setMode
(
'width'
)
.
callbacks
.
once
(
function
()
{
state
.
trigger
(
'videoCaption.resize'
,
null
);
});
$
(
window
).
bind
(
'resize'
,
_
.
debounce
(
state
.
resizer
.
align
,
100
));
}
// function _restartUsingFlash(state)
//
// When we are about to play a YouTube video in HTML5 mode and discover
...
...
@@ -393,6 +416,16 @@ function (HTML5Video, Resizer) {
var
availablePlaybackRates
,
baseSpeedSubs
,
_this
,
player
,
videoWidth
,
videoHeight
;
dfd
.
resolve
();
if
(
this
.
videoType
===
'html5'
)
{
player
=
this
.
videoEl
=
this
.
videoPlayer
.
player
.
videoEl
;
videoWidth
=
player
[
0
].
videoWidth
||
player
.
width
();
videoHeight
=
player
[
0
].
videoHeight
||
player
.
height
();
_resize
(
this
,
videoWidth
,
videoHeight
);
}
this
.
videoPlayer
.
log
(
'load_video'
);
availablePlaybackRates
=
this
.
videoPlayer
.
player
...
...
@@ -468,27 +501,6 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
player
.
setPlaybackRate
(
this
.
speed
);
}
if
(
this
.
videoType
===
'html5'
)
{
player
=
this
.
videoEl
=
this
.
videoPlayer
.
player
.
videoEl
;
videoWidth
=
player
[
0
].
videoWidth
||
player
.
width
();
videoHeight
=
player
[
0
].
videoHeight
||
player
.
height
();
}
else
{
player
=
this
.
videoEl
=
this
.
el
.
find
(
'iframe'
);
videoWidth
=
player
.
attr
(
'width'
)
||
player
.
width
();
videoHeight
=
player
.
attr
(
'height'
)
||
player
.
height
();
}
this
.
resizer
=
new
Resizer
({
element
:
this
.
videoEl
,
elementRatio
:
videoWidth
/
videoHeight
,
container
:
this
.
videoEl
.
parent
()
})
.
setMode
(
'width'
);
this
.
trigger
(
'videoCaption.resize'
,
null
);
$
(
window
).
bind
(
'resize'
,
_
.
debounce
(
this
.
resizer
.
align
,
100
));
/* The following has been commented out to make sure autoplay is
disabled for students.
if (
...
...
common/lib/xmodule/xmodule/js/src/video/04_video_control.js
View file @
153bc25d
...
...
@@ -8,11 +8,16 @@ function () {
// VideoControl() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoControl
=
{};
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
View file @
153bc25d
...
...
@@ -8,6 +8,8 @@ function () {
// VideoQualityControl() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
// Changing quality for now only works for YouTube videos.
if
(
state
.
videoType
!==
'youtube'
)
{
return
;
...
...
@@ -18,6 +20,9 @@ function () {
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
View file @
153bc25d
...
...
@@ -12,14 +12,18 @@ define(
'video/06_video_progress_slider.js'
,
[],
function
()
{
// VideoProgressSlider() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoProgressSlider
=
{};
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
// No callbacks to DOM events (click, mousemove, etc.).
dfd
.
resolve
();
return
dfd
.
promise
();
};
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
View file @
153bc25d
...
...
@@ -8,11 +8,16 @@ function () {
// VideoVolumeControl() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoVolumeControl
=
{};
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
153bc25d
...
...
@@ -8,15 +8,12 @@ function () {
// VideoSpeedControl() function - what this module "exports".
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoSpeedControl
=
{};
if
(
state
.
videoType
===
'html5'
)
{
_initialize
(
state
);
}
else
if
(
state
.
videoType
===
'youtube'
&&
state
.
youtubeXhr
)
{
state
.
youtubeXhr
.
always
(
function
()
{
_initialize
(
state
);
});
}
dfd
.
resolve
();
if
(
state
.
videoType
===
'html5'
&&
!
(
_checkPlaybackRates
()))
{
console
.
log
(
...
...
@@ -24,9 +21,9 @@ function () {
);
_hideSpeedControl
(
state
);
return
;
}
return
dfd
.
promise
();
};
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
View file @
153bc25d
...
...
@@ -21,11 +21,16 @@ function () {
* @returns {undefined}
*/
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoCaption
=
{};
_makeFunctionsPublic
(
state
);
state
.
videoCaption
.
renderElements
();
dfd
.
resolve
();
return
dfd
.
promise
();
};
// ***************************************************************
...
...
@@ -725,7 +730,7 @@ function () {
});
}
if
(
this
.
resizer
)
{
if
(
this
.
resizer
&&
!
this
.
isFullScreen
)
{
this
.
resizer
.
alignByWidthOnly
();
}
...
...
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
153bc25d
...
...
@@ -94,20 +94,22 @@ function (
state
=
{};
previousState
=
state
;
state
.
modules
=
[
FocusGrabber
,
VideoControl
,
VideoQualityControl
,
VideoProgressSlider
,
VideoVolumeControl
,
VideoSpeedControl
,
VideoCaption
];
state
.
youtubeXhr
=
youtubeXhr
;
Initialize
(
state
,
element
);
if
(
!
youtubeXhr
)
{
youtubeXhr
=
state
.
youtubeXhr
;
}
FocusGrabber
(
state
);
VideoControl
(
state
);
VideoQualityControl
(
state
);
VideoProgressSlider
(
state
);
VideoVolumeControl
(
state
);
VideoSpeedControl
(
state
);
VideoCaption
(
state
);
// Because the 'state' object is only available inside this closure, we will also make
// it available to the caller by returning it. This is necessary so that we can test
// Video with Jasmine.
...
...
common/lib/xmodule/xmodule/static_content.py
View file @
153bc25d
...
...
@@ -98,6 +98,7 @@ def _write_styles(selector, output_root, classes):
module_styles_lines
=
[]
module_styles_lines
.
append
(
"@import 'bourbon/bourbon';"
)
module_styles_lines
.
append
(
"@import 'bourbon/addons/button';"
)
module_styles_lines
.
append
(
"@import 'assets/anims';"
)
for
class_
,
fragment_names
in
css_imports
.
items
():
module_styles_lines
.
append
(
"""{selector}.xmodule_{class_} {{"""
.
format
(
class_
=
class_
,
selector
=
selector
...
...
common/static/sass/assets/_anims.scss
0 → 100644
View file @
153bc25d
// animations & keyframes
// ====================
// fade in
@include
keyframes
(
fadeIn
)
{
0
%
{
opacity
:
0
.0
;
}
50
%
{
opacity
:
0
.5
;
}
100
%
{
opacity
:
1
.0
;
}
}
// fade out
@include
keyframes
(
fadeOut
)
{
0
%
{
opacity
:
1
.0
;
}
50
%
{
opacity
:
0
.5
;
}
100
%
{
opacity
:
0
.0
;
}
}
// ====================
// rotate up
@include
keyframes
(
rotateUp
)
{
0
%
{
@include
transform
(
rotate
(
0deg
));
}
50
%
{
@include
transform
(
rotate
(
-90deg
));
}
100
%
{
@include
transform
(
rotate
(
-180deg
));
}
}
// rotate up
@include
keyframes
(
rotateDown
)
{
0
%
{
@include
transform
(
rotate
(
0deg
));
}
50
%
{
@include
transform
(
rotate
(
90deg
));
}
100
%
{
@include
transform
(
rotate
(
180deg
));
}
}
// rotate clockwise
@include
keyframes
(
rotateCW
)
{
0
%
{
@include
transform
(
rotate
(
0deg
));
}
50
%
{
@include
transform
(
rotate
(
180deg
));
}
100
%
{
@include
transform
(
rotate
(
360deg
));
}
}
// rotate counter-clockwise
@include
keyframes
(
rotateCCW
)
{
0
%
{
@include
transform
(
rotate
(
0deg
));
}
50
%
{
@include
transform
(
rotate
(
-180deg
));
}
100
%
{
@include
transform
(
rotate
(
-360deg
));
}
}
// bounce in
@include
keyframes
(
bounceIn
)
{
0
%
{
opacity
:
0
.0
;
@include
transform
(
scale
(
0
.3
));
}
50
%
{
opacity
:
1
.0
;
@include
transform
(
scale
(
1
.05
));
}
100
%
{
@include
transform
(
scale
(
1
));
}
}
// bounce out
@include
keyframes
(
bounceOut
)
{
0
%
{
@include
transform
(
scale
(
1
));
}
50
%
{
opacity
:
1
.0
;
@include
transform
(
scale
(
1
.05
));
}
100
%
{
opacity
:
0
.0
;
@include
transform
(
scale
(
0
.3
));
}
}
// ====================
// flash
@include
keyframes
(
flash
)
{
0
%
,
100
%
{
opacity
:
1
.0
;
}
50
%
{
opacity
:
0
.0
;
}
}
// flash - double
@include
keyframes
(
flashDouble
)
{
0
%
,
50
%
,
100
%
{
opacity
:
1
.0
;
}
25
%
,
75
%
{
opacity
:
0
.0
;
}
}
lms/templates/video.html
View file @
153bc25d
...
...
@@ -47,15 +47,13 @@
<a
href=
"#before-transcript"
class=
"nav-skip"
>
${_("Skip to a navigable version of this video's transcript.")}
</a>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<div
id=
"${id}"
></div>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
</section>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<div
class=
"slider"
title=
"Video position"
></div>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment