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
64c0a8c9
Commit
64c0a8c9
authored
Nov 15, 2013
by
polesye
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1642 from edx/anton/video-player-improvements
Video player: improvements.
parents
50250d82
153bc25d
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 @
64c0a8c9
...
@@ -5,6 +5,13 @@ These are notable changes in edx-platform. This is a rolling list of changes,
...
@@ -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
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
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).
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
Blades: Fix answer choices rearranging if user tries to stylize something in the
...
...
cms/djangoapps/contentstore/features/video.feature
View file @
64c0a8c9
...
@@ -46,11 +46,11 @@ Feature: CMS.Video Component
...
@@ -46,11 +46,11 @@ Feature: CMS.Video Component
Scenario
:
Closed captions become visible when the mouse hovers over CC button
Scenario
:
Closed captions become visible when the mouse hovers over CC button
Given
I have created a Video component with subtitles
Given
I have created a Video component with subtitles
And
Make sure captions are closed
And
Make sure captions are closed
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
And
I hover over button
"CC"
And
I hover over button
"CC"
Then
Captions become
"visible"
Then
Captions become
"visible"
And
I hover over button
"volume"
And
I hover over button
"volume"
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
# 8
# 8
Scenario
:
Open captions never become invisible
Scenario
:
Open captions never become invisible
...
@@ -66,7 +66,7 @@ Feature: CMS.Video Component
...
@@ -66,7 +66,7 @@ Feature: CMS.Video Component
Scenario
:
Closed captions are invisible when mouse doesn't hover on CC button
Scenario
:
Closed captions are invisible when mouse doesn't hover on CC button
Given
I have created a Video component with subtitles
Given
I have created a Video component with subtitles
And
Make sure captions are closed
And
Make sure captions are closed
Then
Captions become
"invisible"
after 3 seconds
Then
Captions become
"invisible"
And
I hover over button
"volume"
And
I hover over button
"volume"
Then
Captions are
"invisible"
Then
Captions are
"invisible"
...
@@ -74,9 +74,9 @@ Feature: CMS.Video Component
...
@@ -74,9 +74,9 @@ Feature: CMS.Video Component
Scenario
:
When
enter key is pressed on a caption shows an outline around it
Scenario
:
When
enter key is pressed on a caption shows an outline around it
Given
I have created a Video component with subtitles
Given
I have created a Video component with subtitles
And
Make sure captions are opened
And
Make sure captions are opened
Then
I focus on caption line with data-index
0
Then
I focus on caption line with data-index
"0"
Then
I press
"enter"
button 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"
And
I see caption line with data-index
"0"
has class
"focused"
# 11
# 11
# Disabled until we come up with a more solid test, as this one is brittle.
# Disabled until we come up with a more solid test, as this one is brittle.
...
...
cms/djangoapps/contentstore/features/video.py
View file @
64c0a8c9
...
@@ -11,6 +11,11 @@ VIDEO_BUTTONS = {
...
@@ -11,6 +11,11 @@ VIDEO_BUTTONS = {
'Play'
:
'.video_control.play'
,
'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.
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY
=
0.5
DELAY
=
0.5
...
@@ -23,6 +28,13 @@ def i_created_a_video_component(step):
...
@@ -23,6 +28,13 @@ def i_created_a_video_component(step):
category
=
'video'
,
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$'
)
@step
(
'I have created a Video component with subtitles$'
)
def
i_created_a_video_with_subs
(
_step
):
def
i_created_a_video_with_subs
(
_step
):
...
@@ -41,7 +53,13 @@ def i_created_a_video_with_subs_with_name(_step, sub_id):
...
@@ -41,7 +53,13 @@ def i_created_a_video_with_subs_with_name(_step, sub_id):
# Return to the video
# Return to the video
world
.
visit
(
video_url
)
world
.
visit
(
video_url
)
world
.
wait_for_xmodule
()
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 "([^"]*)"$'
)
@step
(
'I have uploaded subtitles "([^"]*)"$'
)
...
@@ -52,7 +70,6 @@ def i_have_uploaded_subtitles(_step, sub_id):
...
@@ -52,7 +70,6 @@ def i_have_uploaded_subtitles(_step, sub_id):
@step
(
'when I view the (.*) it does not have autoplay enabled$'
)
@step
(
'when I view the (.*) it does not have autoplay enabled$'
)
def
does_not_autoplay
(
_step
,
video_type
):
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_find
(
'.
%
s'
%
video_type
)[
0
][
'data-autoplay'
]
==
'False'
assert
world
.
css_has_class
(
'.video_control'
,
'play'
)
assert
world
.
css_has_class
(
'.video_control'
,
'play'
)
...
@@ -73,7 +90,6 @@ def i_edit_the_component(_step):
...
@@ -73,7 +90,6 @@ def i_edit_the_component(_step):
@step
(
'I have (hidden|toggled) captions$'
)
@step
(
'I have (hidden|toggled) captions$'
)
def
hide_or_show_captions
(
step
,
shown
):
def
hide_or_show_captions
(
step
,
shown
):
world
.
wait_for_xmodule
()
button_css
=
'a.hide-subtitles'
button_css
=
'a.hide-subtitles'
if
shown
==
'hidden'
:
if
shown
==
'hidden'
:
world
.
css_click
(
button_css
)
world
.
css_click
(
button_css
)
...
@@ -118,18 +134,18 @@ def xml_only_video(step):
...
@@ -118,18 +134,18 @@ def xml_only_video(step):
@step
(
'The correct Youtube video is shown$'
)
@step
(
'The correct Youtube video is shown$'
)
def
the_youtube_video_is_shown
(
_step
):
def
the_youtube_video_is_shown
(
_step
):
world
.
wait_for_xmodule
()
ele
=
world
.
css_find
(
'.video'
)
.
first
ele
=
world
.
css_find
(
'.video'
)
.
first
assert
ele
[
'data-streams'
]
.
split
(
':'
)[
1
]
==
world
.
scenario_dict
[
'YOUTUBE_ID'
]
assert
ele
[
'data-streams'
]
.
split
(
':'
)[
1
]
==
world
.
scenario_dict
[
'YOUTUBE_ID'
]
@step
(
'Make sure captions are (.+)$'
)
@step
(
'Make sure captions are (.+)$'
)
def
set_captions_visibility_state
(
_step
,
captions_state
):
def
set_captions_visibility_state
(
_step
,
captions_state
):
SELECTOR
=
'.closed .subtitles'
if
captions_state
==
'closed'
:
if
captions_state
==
'closed'
:
if
world
.
css_visible
(
'.subtitles'
):
if
not
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
else
:
else
:
if
not
world
.
css_visible
(
'.subtitles'
):
if
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
...
@@ -139,18 +155,7 @@ def hover_over_button(_step, button):
...
@@ -139,18 +155,7 @@ def hover_over_button(_step, button):
@step
(
'Captions (?:are|become) "([^"]*)"$'
)
@step
(
'Captions (?:are|become) "([^"]*)"$'
)
def
are_captions_visibile
(
_step
,
visibility_state
):
def
check_captions_visibility_state
(
_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
)
if
visibility_state
==
'visible'
:
if
visibility_state
==
'visible'
:
assert
world
.
css_visible
(
'.subtitles'
)
assert
world
.
css_visible
(
'.subtitles'
)
else
:
else
:
...
@@ -162,17 +167,17 @@ def find_caption_line_by_data_index(index):
...
@@ -162,17 +167,17 @@ def find_caption_line_by_data_index(index):
return
world
.
css_find
(
SELECTOR
)
.
first
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
):
def
focus_on_caption_line
(
_step
,
index
):
find_caption_line_by_data_index
(
int
(
index
.
strip
()))
.
_element
.
send_keys
(
Keys
.
TAB
)
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+)
$'
)
@step
(
'I press "enter" button on caption line with data-index
"([^"]*)"
$'
)
def
focus_on_caption_line
(
_step
,
index
):
def
click_on_the_caption
(
_step
,
index
):
find_caption_line_by_data_index
(
int
(
index
.
strip
()))
.
_element
.
send_keys
(
Keys
.
ENTER
)
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
):
def
caption_line_has_class
(
_step
,
index
,
className
):
SELECTOR
=
".subtitles > li[data-index='{index}']"
.
format
(
index
=
int
(
index
.
strip
()))
SELECTOR
=
".subtitles > li[data-index='{index}']"
.
format
(
index
=
int
(
index
.
strip
()))
world
.
css_has_class
(
SELECTOR
,
className
.
strip
())
world
.
css_has_class
(
SELECTOR
,
className
.
strip
())
...
...
cms/djangoapps/contentstore/views/item.py
View file @
64c0a8c9
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
logging
import
logging
from
uuid
import
uuid4
from
uuid
import
uuid4
from
static_replace
import
replace_static_urls
from
static_replace
import
replace_static_urls
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
64c0a8c9
...
@@ -15,9 +15,17 @@ div.video {
...
@@ -15,9 +15,17 @@ div.video {
border
:
0
;
border
:
0
;
}
}
&
.is-initialized
{
article
.video-wrapper
{
.spinner
{
display
:
none
;
}
}
}
div
.tc-wrapper
{
div
.tc-wrapper
{
position
:
relative
;
@include
clearfix
;
@include
clearfix
;
position
:
relative
;
}
}
div
.focus_grabber
{
div
.focus_grabber
{
...
@@ -58,21 +66,38 @@ div.video {
...
@@ -58,21 +66,38 @@ div.video {
float
:
left
;
float
:
left
;
margin-right
:
flex-gutter
(
9
);
margin-right
:
flex-gutter
(
9
);
width
:
flex-grid
(
6
,
9
);
width
:
flex-grid
(
6
,
9
);
background-color
:
black
;
background-color
:
black
;
position
:
relative
;
position
:
relative
;
div
.video-player-pre
{
div
.video-player-pre
,
div
.video-player-post
{
height
:
50px
;
height
:
50px
;
background-color
:
black
;
background-color
:
black
;
}
}
div
.video-player-post
{
.spinner
{
height
:
50px
;
@include
transform
(
translate
(
-50%
,
-50%
));
background-color
:
black
;
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
{
section
.video-player
{
overflow
:
hidden
;
overflow
:
hidden
;
min-height
:
300px
;
min-height
:
300px
;
...
@@ -85,7 +110,6 @@ div.video {
...
@@ -85,7 +110,6 @@ div.video {
object
,
iframe
,
video
{
object
,
iframe
,
video
{
border
:
none
;
border
:
none
;
height
:
100%
;
width
:
100%
;
width
:
100%
;
}
}
...
@@ -109,12 +133,13 @@ div.video {
...
@@ -109,12 +133,13 @@ div.video {
&
:hover
{
&
:hover
{
ul
,
div
{
ul
,
div
{
opacity
:
1
.0
;
opacity
:
1
;
}
}
}
}
div
.slider
{
div
.slider
{
@include
clearfix
();
@include
clearfix
();
@include
transform
(
scaleY
(
0
.5
)
translate3d
(
0
,
50%
,
0
));
background
:
#c2c2c2
;
background
:
#c2c2c2
;
border
:
1px
solid
#000
;
border
:
1px
solid
#000
;
border-radius
:
0
;
border-radius
:
0
;
...
@@ -132,7 +157,6 @@ div.video {
...
@@ -132,7 +157,6 @@ div.video {
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
transition
:
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
{
div
.ui-widget-header
{
background
:
#777
;
background
:
#777
;
...
@@ -141,23 +165,11 @@ div.video {
...
@@ -141,23 +165,11 @@ div.video {
div
.ui-corner-all.slider-range
{
div
.ui-corner-all.slider-range
{
background-color
:
#1e91d3
;
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
;
opacity
:
0
.3
;
}
}
a
.ui-slider-handle
{
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
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
background-size
:
50%
;
background-size
:
50%
;
border
:
1px
solid
darken
(
$pink
,
20%
);
border
:
1px
solid
darken
(
$pink
,
20%
);
...
@@ -171,7 +183,6 @@ div.video {
...
@@ -171,7 +183,6 @@ div.video {
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-moz-transition
:
-
moz-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
-ms-transition
:
-
ms-transform
0
.7s
ease-in-out
;
transition
:
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
;
width
:
20px
;
&
:focus
,
&
:hover
{
&
:focus
,
&
:hover
{
...
@@ -268,25 +279,26 @@ div.video {
...
@@ -268,25 +279,26 @@ div.video {
position
:
relative
;
position
:
relative
;
&
.open
{
&
.open
{
&
>
a
{
&
>
a
{
background
:
url('../images/open-arrow.png')
10px
center
no-repeat
;
background
:
url('../images/open-arrow.png')
10px
center
no-repeat
;
}
}
ol
.video_speeds
{
ol
.video_speeds
{
display
:
block
;
display
:
block
;
opacity
:
1
.0
;
opacity
:
1
;
padding
:
0
;
padding
:
0
;
margin
:
0
;
margin
:
0
;
list-style
:
none
;
list-style
:
none
;
}
}
}
}
&
>
a
{
&
>
a
{
@include
clearfix
();
@include
transition
(
none
);
background
:
url('../images/closed-arrow.png')
10px
center
no-repeat
;
background
:
url('../images/closed-arrow.png')
10px
center
no-repeat
;
border-left
:
1px
solid
#000
;
border-left
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
@include
clearfix
();
color
:
#fff
;
color
:
#fff
;
cursor
:
pointer
;
cursor
:
pointer
;
display
:
block
;
display
:
block
;
...
@@ -294,7 +306,6 @@ div.video {
...
@@ -294,7 +306,6 @@ div.video {
margin-right
:
0
;
margin-right
:
0
;
padding-left
:
15px
;
padding-left
:
15px
;
position
:
relative
;
position
:
relative
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
-webkit-font-smoothing
:
antialiased
;
width
:
116px
;
width
:
116px
;
...
@@ -312,12 +323,12 @@ div.video {
...
@@ -312,12 +323,12 @@ div.video {
&
:hover
{
&
:hover
{
outline
:
0
;
outline
:
0
;
opacity
:
1
.0
;
opacity
:
1
;
background-color
:
#444
;
background-color
:
#444
;
}
}
&
:active
{
&
:active
{
opacity
:
1
.0
;
opacity
:
1
;
background-color
:
#444
;
background-color
:
#444
;
}
}
...
@@ -349,13 +360,13 @@ div.video {
...
@@ -349,13 +360,13 @@ div.video {
// fix for now
// fix for now
ol
.video_speeds
{
ol
.video_speeds
{
box-shadow
:
inset
1px
0
0
#555
,
0
4px
0
#444
;
@include
transition
(
none
);
@include
transition
(
none
);
box-shadow
:
inset
1px
0
0
#555
,
0
4px
0
#444
;
background-color
:
#444
;
background-color
:
#444
;
border
:
1px
solid
#000
;
border
:
1px
solid
#000
;
bottom
:
46px
;
bottom
:
46px
;
display
:
none
;
display
:
none
;
opacity
:
0
.0
;
opacity
:
0
;
position
:
absolute
;
position
:
absolute
;
width
:
131px
;
width
:
131px
;
...
@@ -404,24 +415,24 @@ div.video {
...
@@ -404,24 +415,24 @@ div.video {
&
.open
{
&
.open
{
.volume-slider-container
{
.volume-slider-container
{
display
:
block
;
display
:
block
;
opacity
:
1
.0
;
opacity
:
1
;
}
}
}
}
&
.muted
{
&
.muted
{
&
>
a
{
&
>
a
{
background-image
:
url('../images/mute.png')
;
background-image
:
url('../images/mute.png')
;
}
}
}
}
>
a
{
&
>
a
{
@include
clearfix
();
@include
transition
(
none
);
background-image
:
url('../images/volume.png')
;
background-image
:
url('../images/volume.png')
;
background-position
:
10px
center
;
background-position
:
10px
center
;
background-repeat
:
no-repeat
;
background-repeat
:
no-repeat
;
border-right
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
@include
clearfix
();
color
:
#fff
;
color
:
#fff
;
cursor
:
pointer
;
cursor
:
pointer
;
display
:
block
;
display
:
block
;
...
@@ -429,7 +440,6 @@ div.video {
...
@@ -429,7 +440,6 @@ div.video {
margin-right
:
0
;
margin-right
:
0
;
padding-left
:
15px
;
padding-left
:
15px
;
position
:
relative
;
position
:
relative
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
width
:
30px
;
...
@@ -442,13 +452,13 @@ div.video {
...
@@ -442,13 +452,13 @@ div.video {
}
}
.volume-slider-container
{
.volume-slider-container
{
box-shadow
:
inset
1px
0
0
#555
,
0
3px
0
#444
;
@include
transition
(
none
);
@include
transition
(
none
);
box-shadow
:
inset
1px
0
0
#555
,
0
3px
0
#444
;
background-color
:
#444
;
background-color
:
#444
;
border
:
1px
solid
#000
;
border
:
1px
solid
#000
;
bottom
:
46px
;
bottom
:
46px
;
display
:
none
;
display
:
none
;
opacity
:
0
.0
;
opacity
:
0
;
position
:
absolute
;
position
:
absolute
;
width
:
45px
;
width
:
45px
;
height
:
125px
;
height
:
125px
;
...
@@ -465,6 +475,7 @@ div.video {
...
@@ -465,6 +475,7 @@ div.video {
box-shadow
:
0
1px
0
#333
;
box-shadow
:
0
1px
0
#333
;
a
.ui-slider-handle
{
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
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
background-size
:
50%
;
background-size
:
50%
;
border
:
1px
solid
darken
(
$pink
,
20%
);
border
:
1px
solid
darken
(
$pink
,
20%
);
...
@@ -473,7 +484,6 @@ div.video {
...
@@ -473,7 +484,6 @@ div.video {
cursor
:
pointer
;
cursor
:
pointer
;
height
:
15px
;
height
:
15px
;
left
:
-6px
;
left
:
-6px
;
@include
transition
(
height
2
.0s
ease-in-out
0s
,
width
2
.0s
ease-in-out
0s
);
width
:
15px
;
width
:
15px
;
}
}
...
@@ -485,6 +495,7 @@ div.video {
...
@@ -485,6 +495,7 @@ div.video {
}
}
a
.add-fullscreen
{
a
.add-fullscreen
{
@include
transition
(
none
);
background
:
url(../images/fullscreen.png)
center
no-repeat
;
background
:
url(../images/fullscreen.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
...
@@ -495,7 +506,6 @@ div.video {
...
@@ -495,7 +506,6 @@ div.video {
margin-left
:
0
;
margin-left
:
0
;
padding
:
0
lh
(
.5
);
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
text-indent
:
-9999px
;
@include
transition
(
none
);
width
:
30px
;
width
:
30px
;
&
:hover
,
&
:active
{
&
:hover
,
&
:active
{
...
@@ -508,6 +518,7 @@ div.video {
...
@@ -508,6 +518,7 @@ div.video {
a
.quality_control
{
a
.quality_control
{
@include
transition
(
none
);
background
:
url(../images/hd.png)
center
no-repeat
;
background
:
url(../images/hd.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
box-shadow
:
1px
0
0
#555
,
inset
1px
0
0
#555
;
...
@@ -518,7 +529,6 @@ div.video {
...
@@ -518,7 +529,6 @@ div.video {
margin-left
:
0
;
margin-left
:
0
;
padding
:
0
lh
(
.5
);
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
text-indent
:
-9999px
;
@include
transition
(
none
);
width
:
30px
;
width
:
30px
;
&
:hover
{
&
:hover
{
...
@@ -538,16 +548,16 @@ div.video {
...
@@ -538,16 +548,16 @@ div.video {
a
.hide-subtitles
{
a
.hide-subtitles
{
@include
transition
(
none
);
background
:
url('../images/cc.png')
center
no-repeat
;
background
:
url('../images/cc.png')
center
no-repeat
;
float
:
left
;
float
:
left
;
font-weight
:
800
;
font-weight
:
800
;
line-height
:
46px
;
//height of play pause buttons
line-height
:
46px
;
//height of play pause buttons
margin-left
:
0
;
margin-left
:
0
;
opacity
:
1
.0
;
opacity
:
1
;
padding
:
0
lh
(
.5
);
padding
:
0
lh
(
.5
);
position
:
relative
;
position
:
relative
;
text-indent
:
-9999px
;
text-indent
:
-9999px
;
@include
transition
(
none
);
-webkit-font-smoothing
:
antialiased
;
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
width
:
30px
;
...
@@ -569,7 +579,7 @@ div.video {
...
@@ -569,7 +579,7 @@ div.video {
&
:hover
section
.video-controls
{
&
:hover
section
.video-controls
{
ul
,
div
{
ul
,
div
{
opacity
:
1
.0
;
opacity
:
1
;
}
}
div
.slider
{
div
.slider
{
...
@@ -732,6 +742,7 @@ div.video {
...
@@ -732,6 +742,7 @@ div.video {
}
}
ol
.subtitles
{
ol
.subtitles
{
@include
transition
(
none
);
background
:
rgba
(
#000
,
.8
);
background
:
rgba
(
#000
,
.8
);
bottom
:
0
;
bottom
:
0
;
height
:
100%
;
height
:
100%
;
...
@@ -742,7 +753,6 @@ div.video {
...
@@ -742,7 +753,6 @@ div.video {
right
:
0
;
right
:
0
;
top
:
0
;
top
:
0
;
visibility
:
visible
;
visibility
:
visible
;
@include
transition
(
none
);
li
{
li
{
color
:
#aaa
;
color
:
#aaa
;
...
@@ -754,3 +764,5 @@ div.video {
...
@@ -754,3 +764,5 @@ div.video {
}
}
}
}
}
}
common/lib/xmodule/xmodule/js/spec/video/resizer_spec.js
View file @
64c0a8c9
...
@@ -97,6 +97,85 @@ function (Resizer) {
...
@@ -97,6 +97,85 @@ function (Resizer) {
expect
(
realWidth
).
toBe
(
expectedWidth
);
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 @
64c0a8c9
...
@@ -12,6 +12,8 @@ function () {
...
@@ -12,6 +12,8 @@ function () {
containerRatio
:
null
,
containerRatio
:
null
,
elementRatio
:
null
elementRatio
:
null
},
},
callbacksList
=
[],
module
=
{},
mode
=
null
,
mode
=
null
,
config
;
config
;
...
@@ -28,7 +30,7 @@ function () {
...
@@ -28,7 +30,7 @@ function () {
);
);
}
}
return
this
;
return
module
;
};
};
var
getData
=
function
()
{
var
getData
=
function
()
{
...
@@ -79,7 +81,9 @@ function () {
...
@@ -79,7 +81,9 @@ function () {
break
;
break
;
}
}
return
this
;
fireCallbacks
();
return
module
;
};
};
var
alignByWidthOnly
=
function
()
{
var
alignByWidthOnly
=
function
()
{
...
@@ -93,7 +97,7 @@ function () {
...
@@ -93,7 +97,7 @@ function () {
'left'
:
0
'left'
:
0
});
});
return
this
;
return
module
;
};
};
var
alignByHeightOnly
=
function
()
{
var
alignByHeightOnly
=
function
()
{
...
@@ -107,7 +111,7 @@ function () {
...
@@ -107,7 +111,7 @@ function () {
'left'
:
0.5
*
(
data
.
containerWidth
-
width
)
'left'
:
0.5
*
(
data
.
containerWidth
-
width
)
});
});
return
this
;
return
module
;
};
};
var
setMode
=
function
(
param
)
{
var
setMode
=
function
(
param
)
{
...
@@ -116,18 +120,69 @@ function () {
...
@@ -116,18 +120,69 @@ function () {
align
();
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
,
align
:
align
,
alignByWidthOnly
:
alignByWidthOnly
,
alignByWidthOnly
:
alignByWidthOnly
,
alignByHeightOnly
:
alignByHeightOnly
,
alignByHeightOnly
:
alignByHeightOnly
,
setParams
:
initialize
,
setParams
:
initialize
,
setMode
:
setMode
setMode
:
setMode
,
};
callbacks
:
{
add
:
addCallback
,
once
:
addOnceCallback
,
remove
:
removeCallback
,
removeAll
:
removeCallbacks
}
});
};
};
return
Resizer
;
return
Resizer
;
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
64c0a8c9
...
@@ -16,7 +16,6 @@ define(
...
@@ -16,7 +16,6 @@ define(
'video/01_initialize.js'
,
'video/01_initialize.js'
,
[
'video/03_video_player.js'
],
[
'video/03_video_player.js'
],
function
(
VideoPlayer
)
{
function
(
VideoPlayer
)
{
// window.console.log() is expected to be available. We do not support
// window.console.log() is expected to be available. We do not support
// browsers which lack this functionality.
// browsers which lack this functionality.
...
@@ -42,7 +41,20 @@ function (VideoPlayer) {
...
@@ -42,7 +41,20 @@ function (VideoPlayer) {
*/
*/
return
function
(
state
,
element
)
{
return
function
(
state
,
element
)
{
_makeFunctionsPublic
(
state
);
_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) {
...
@@ -94,12 +106,20 @@ function (VideoPlayer) {
// Require JS. At the time when we reach this code, the stand alone
// 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
// HTML5 player is already loaded, so no further testing in that case
// is required.
// is required.
var
video
;
if
(
state
.
videoType
===
'youtube'
)
{
if
(
state
.
videoType
===
'youtube'
)
{
YT
.
ready
(
function
()
{
YT
.
ready
(
function
()
{
VideoPlayer
(
state
);
video
=
VideoPlayer
(
state
);
state
.
modules
.
push
(
video
);
state
.
__dfd__
.
resolve
();
});
});
}
else
{
}
else
{
VideoPlayer
(
state
);
video
=
VideoPlayer
(
state
);
state
.
modules
.
push
(
video
);
state
.
__dfd__
.
resolve
();
}
}
}
}
...
@@ -191,6 +211,8 @@ function (VideoPlayer) {
...
@@ -191,6 +211,8 @@ function (VideoPlayer) {
state
.
html5Sources
.
mp4
===
null
&&
state
.
html5Sources
.
mp4
===
null
&&
state
.
html5Sources
.
ogg
===
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 div'
).
addClass
(
'hidden'
);
state
.
el
.
find
(
'.video-player h3'
).
removeClass
(
'hidden'
);
state
.
el
.
find
(
'.video-player h3'
).
removeClass
(
'hidden'
);
...
@@ -224,6 +246,22 @@ function (VideoPlayer) {
...
@@ -224,6 +246,22 @@ function (VideoPlayer) {
state
.
captionHideTimeout
=
null
;
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.
// Public functions start here.
// These are available via the 'state' object. Their context ('this'
// These are available via the 'state' object. Their context ('this'
...
@@ -259,6 +297,7 @@ function (VideoPlayer) {
...
@@ -259,6 +297,7 @@ function (VideoPlayer) {
data
,
tempYtTestTimeout
;
data
,
tempYtTestTimeout
;
// This is used in places where we instead would have to check if an
// This is used in places where we instead would have to check if an
// element has a CSS class 'fullscreen'.
// element has a CSS class 'fullscreen'.
this
.
__dfd__
=
$
.
Deferred
();
this
.
isFullScreen
=
false
;
this
.
isFullScreen
=
false
;
// The parent element of the video, and the ID.
// The parent element of the video, and the ID.
...
@@ -313,8 +352,9 @@ function (VideoPlayer) {
...
@@ -313,8 +352,9 @@ function (VideoPlayer) {
// If we do not have YouTube ID's, try parsing HTML5 video sources.
// If we do not have YouTube ID's, try parsing HTML5 video sources.
if
(
!
_prepareHTML5Video
(
this
))
{
if
(
!
_prepareHTML5Video
(
this
))
{
this
.
__dfd__
.
reject
();
// Non-YouTube sources were not found either.
// Non-YouTube sources were not found either.
return
;
return
this
.
__dfd__
.
promise
()
;
}
}
console
.
log
(
'[Video info]: Start player in HTML5 mode.'
);
console
.
log
(
'[Video info]: Start player in HTML5 mode.'
);
...
@@ -381,6 +421,8 @@ function (VideoPlayer) {
...
@@ -381,6 +421,8 @@ function (VideoPlayer) {
_renderElements
(
_this
);
_renderElements
(
_this
);
});
});
}
}
return
this
.
__dfd__
.
promise
();
}
}
/*
/*
...
...
common/lib/xmodule/xmodule/js/src/video/025_focus_grabber.js
View file @
64c0a8c9
...
@@ -33,11 +33,16 @@ define(
...
@@ -33,11 +33,16 @@ define(
[],
[],
function
()
{
function
()
{
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
focusGrabber
=
{};
state
.
focusGrabber
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
};
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
64c0a8c9
...
@@ -5,14 +5,18 @@ define(
...
@@ -5,14 +5,18 @@ define(
'video/03_video_player.js'
,
'video/03_video_player.js'
,
[
'video/02_html5_video.js'
,
'video/00_resizer.js'
],
[
'video/02_html5_video.js'
,
'video/00_resizer.js'
],
function
(
HTML5Video
,
Resizer
)
{
function
(
HTML5Video
,
Resizer
)
{
var
dfd
=
$
.
Deferred
();
// VideoPlayer() function - what this module "exports".
// VideoPlayer() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
state
.
videoPlayer
=
{};
state
.
videoPlayer
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_initialize
(
state
);
_initialize
(
state
);
// No callbacks to DOM events (click, mousemove, etc.).
// No callbacks to DOM events (click, mousemove, etc.).
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
@@ -56,7 +60,7 @@ function (HTML5Video, Resizer) {
...
@@ -56,7 +60,7 @@ function (HTML5Video, Resizer) {
// via the 'state' object. Much easier to work this way - you don't
// via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
// have to do repeated jQuery element selects.
function
_initialize
(
state
)
{
function
_initialize
(
state
)
{
var
youTubeId
;
var
youTubeId
,
player
,
videoWidth
,
videoHeight
;
// The function is called just once to apply pre-defined configurations
// The function is called just once to apply pre-defined configurations
// by student before video starts playing. Waits until the video's
// by student before video starts playing. Waits until the video's
...
@@ -138,9 +142,28 @@ function (HTML5Video, Resizer) {
...
@@ -138,9 +142,28 @@ function (HTML5Video, Resizer) {
.
onPlaybackQualityChange
.
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)
// function _restartUsingFlash(state)
//
//
// When we are about to play a YouTube video in HTML5 mode and discover
// When we are about to play a YouTube video in HTML5 mode and discover
...
@@ -393,6 +416,16 @@ function (HTML5Video, Resizer) {
...
@@ -393,6 +416,16 @@ function (HTML5Video, Resizer) {
var
availablePlaybackRates
,
baseSpeedSubs
,
_this
,
var
availablePlaybackRates
,
baseSpeedSubs
,
_this
,
player
,
videoWidth
,
videoHeight
;
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'
);
this
.
videoPlayer
.
log
(
'load_video'
);
availablePlaybackRates
=
this
.
videoPlayer
.
player
availablePlaybackRates
=
this
.
videoPlayer
.
player
...
@@ -468,27 +501,6 @@ function (HTML5Video, Resizer) {
...
@@ -468,27 +501,6 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
player
.
setPlaybackRate
(
this
.
speed
);
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
/* The following has been commented out to make sure autoplay is
disabled for students.
disabled for students.
if (
if (
...
...
common/lib/xmodule/xmodule/js/src/video/04_video_control.js
View file @
64c0a8c9
...
@@ -8,11 +8,16 @@ function () {
...
@@ -8,11 +8,16 @@ function () {
// VideoControl() function - what this module "exports".
// VideoControl() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoControl
=
{};
state
.
videoControl
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
View file @
64c0a8c9
...
@@ -8,6 +8,8 @@ function () {
...
@@ -8,6 +8,8 @@ function () {
// VideoQualityControl() function - what this module "exports".
// VideoQualityControl() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
// Changing quality for now only works for YouTube videos.
// Changing quality for now only works for YouTube videos.
if
(
state
.
videoType
!==
'youtube'
)
{
if
(
state
.
videoType
!==
'youtube'
)
{
return
;
return
;
...
@@ -18,6 +20,9 @@ function () {
...
@@ -18,6 +20,9 @@ function () {
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
View file @
64c0a8c9
...
@@ -12,14 +12,18 @@ define(
...
@@ -12,14 +12,18 @@ define(
'video/06_video_progress_slider.js'
,
'video/06_video_progress_slider.js'
,
[],
[],
function
()
{
function
()
{
// VideoProgressSlider() function - what this module "exports".
// VideoProgressSlider() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoProgressSlider
=
{};
state
.
videoProgressSlider
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_renderElements
(
state
);
// No callbacks to DOM events (click, mousemove, etc.).
// 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 @
64c0a8c9
...
@@ -8,11 +8,16 @@ function () {
...
@@ -8,11 +8,16 @@ function () {
// VideoVolumeControl() function - what this module "exports".
// VideoVolumeControl() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoVolumeControl
=
{};
state
.
videoVolumeControl
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
_bindHandlers
(
state
);
dfd
.
resolve
();
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
64c0a8c9
...
@@ -8,15 +8,12 @@ function () {
...
@@ -8,15 +8,12 @@ function () {
// VideoSpeedControl() function - what this module "exports".
// VideoSpeedControl() function - what this module "exports".
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoSpeedControl
=
{};
state
.
videoSpeedControl
=
{};
if
(
state
.
videoType
===
'html5'
)
{
_initialize
(
state
);
}
else
if
(
state
.
videoType
===
'youtube'
&&
state
.
youtubeXhr
)
{
state
.
youtubeXhr
.
always
(
function
()
{
_initialize
(
state
);
_initialize
(
state
);
});
dfd
.
resolve
();
}
if
(
state
.
videoType
===
'html5'
&&
!
(
_checkPlaybackRates
()))
{
if
(
state
.
videoType
===
'html5'
&&
!
(
_checkPlaybackRates
()))
{
console
.
log
(
console
.
log
(
...
@@ -24,9 +21,9 @@ function () {
...
@@ -24,9 +21,9 @@ function () {
);
);
_hideSpeedControl
(
state
);
_hideSpeedControl
(
state
);
return
;
}
}
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
...
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
View file @
64c0a8c9
...
@@ -21,11 +21,16 @@ function () {
...
@@ -21,11 +21,16 @@ function () {
* @returns {undefined}
* @returns {undefined}
*/
*/
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
state
.
videoCaption
=
{};
state
.
videoCaption
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
state
.
videoCaption
.
renderElements
();
state
.
videoCaption
.
renderElements
();
dfd
.
resolve
();
return
dfd
.
promise
();
};
};
// ***************************************************************
// ***************************************************************
...
@@ -725,7 +730,7 @@ function () {
...
@@ -725,7 +730,7 @@ function () {
});
});
}
}
if
(
this
.
resizer
)
{
if
(
this
.
resizer
&&
!
this
.
isFullScreen
)
{
this
.
resizer
.
alignByWidthOnly
();
this
.
resizer
.
alignByWidthOnly
();
}
}
...
...
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
64c0a8c9
...
@@ -94,20 +94,22 @@ function (
...
@@ -94,20 +94,22 @@ function (
state
=
{};
state
=
{};
previousState
=
state
;
previousState
=
state
;
state
.
modules
=
[
FocusGrabber
,
VideoControl
,
VideoQualityControl
,
VideoProgressSlider
,
VideoVolumeControl
,
VideoSpeedControl
,
VideoCaption
];
state
.
youtubeXhr
=
youtubeXhr
;
state
.
youtubeXhr
=
youtubeXhr
;
Initialize
(
state
,
element
);
Initialize
(
state
,
element
);
if
(
!
youtubeXhr
)
{
if
(
!
youtubeXhr
)
{
youtubeXhr
=
state
.
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
// 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
// it available to the caller by returning it. This is necessary so that we can test
// Video with Jasmine.
// Video with Jasmine.
...
...
common/lib/xmodule/xmodule/static_content.py
View file @
64c0a8c9
...
@@ -98,6 +98,7 @@ def _write_styles(selector, output_root, classes):
...
@@ -98,6 +98,7 @@ def _write_styles(selector, output_root, classes):
module_styles_lines
=
[]
module_styles_lines
=
[]
module_styles_lines
.
append
(
"@import 'bourbon/bourbon';"
)
module_styles_lines
.
append
(
"@import 'bourbon/bourbon';"
)
module_styles_lines
.
append
(
"@import 'bourbon/addons/button';"
)
module_styles_lines
.
append
(
"@import 'bourbon/addons/button';"
)
module_styles_lines
.
append
(
"@import 'assets/anims';"
)
for
class_
,
fragment_names
in
css_imports
.
items
():
for
class_
,
fragment_names
in
css_imports
.
items
():
module_styles_lines
.
append
(
"""{selector}.xmodule_{class_} {{"""
.
format
(
module_styles_lines
.
append
(
"""{selector}.xmodule_{class_} {{"""
.
format
(
class_
=
class_
,
selector
=
selector
class_
=
class_
,
selector
=
selector
...
...
common/static/sass/assets/_anims.scss
0 → 100644
View file @
64c0a8c9
// 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 @
64c0a8c9
...
@@ -47,15 +47,13 @@
...
@@ -47,15 +47,13 @@
<a
href=
"#before-transcript"
class=
"nav-skip"
>
${_("Skip to a navigable version of this video's transcript.")}
</a>
<a
href=
"#before-transcript"
class=
"nav-skip"
>
${_("Skip to a navigable version of this video's transcript.")}
</a>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<div
class=
"video-player-pre"
></div>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"${id}"
></div>
<div
id=
"${id}"
></div>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
</section>
</section>
<div
class=
"video-player-post"
></div>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<section
class=
"video-controls"
>
<div
class=
"slider"
title=
"Video position"
></div>
<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