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
d8144b0d
Commit
d8144b0d
authored
Feb 11, 2014
by
Valera Rozuvan
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2365 from edx/anton/store-video-progress
Anton/store video progress
parents
0b3604f8
102d1cb2
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
656 additions
and
482 deletions
+656
-482
CHANGELOG.rst
+2
-0
cms/djangoapps/contentstore/features/video.feature
+51
-0
cms/djangoapps/contentstore/features/video.py
+14
-0
common/lib/xmodule/xmodule/fields.py
+3
-2
common/lib/xmodule/xmodule/js/fixtures/video.html
+1
-0
common/lib/xmodule/xmodule/js/fixtures/video_all.html
+1
-0
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+1
-0
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
+1
-0
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
+1
-0
common/lib/xmodule/xmodule/js/spec/helper.js
+5
-6
common/lib/xmodule/xmodule/js/spec/video/cookie_storage_spec.js
+0
-149
common/lib/xmodule/xmodule/js/spec/video/events_spec.js
+1
-0
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
+13
-4
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
+1
-1
common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js
+154
-0
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
+2
-3
common/lib/xmodule/xmodule/js/spec/video/video_control_spec.js
+1
-0
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+1
-4
common/lib/xmodule/xmodule/js/spec/video/video_progress_slider_spec.js
+1
-0
common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js
+9
-2
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
+1
-0
common/lib/xmodule/xmodule/js/spec/video/video_storage_spec.js
+83
-0
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
+1
-0
common/lib/xmodule/xmodule/js/src/time.coffee
+13
-0
common/lib/xmodule/xmodule/js/src/video/00_cookie_storage.js
+0
-194
common/lib/xmodule/xmodule/js/src/video/00_video_storage.js
+103
-0
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+68
-40
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+47
-45
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
+7
-12
common/lib/xmodule/xmodule/js/src/video/10_main.js
+6
-13
common/lib/xmodule/xmodule/video_module.py
+15
-7
lms/djangoapps/courseware/features/video.feature
+11
-0
lms/djangoapps/courseware/features/video.py
+33
-0
lms/djangoapps/courseware/tests/test_video_mongo.py
+4
-0
lms/templates/video.html
+1
-0
No files found.
CHANGELOG.rst
View file @
d8144b0d
...
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
...
@@ -5,6 +5,8 @@ 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: Persist student progress in video. BLD-385.
Blades: Fix for the list metadata editor that gets into a bad state where "Add"
Blades: Fix for the list metadata editor that gets into a bad state where "Add"
is disabled. BLD-821.
is disabled. BLD-821.
...
...
cms/djangoapps/contentstore/features/video.feature
View file @
d8144b0d
...
@@ -95,3 +95,54 @@ Feature: CMS.Video Component
...
@@ -95,3 +95,54 @@ Feature: CMS.Video Component
And
I save changes
And
I save changes
And
I click video button
"play"
And
I click video button
"play"
Then
I see a range on slider
Then
I see a range on slider
# 12
Scenario
:
Check that position is stored on page refresh, position within start-end range
Given
I have created a Video component with subtitles
And
Make sure captions are closed
And
I edit the component
And
I open tab
"Advanced"
And I set value "00
:
00
:
12"
to
the
field
"Start
Time"
And I set value "00
:
00
:
24"
to
the
field
"End
Time"
And
I save changes
And
I click video button
"play"
Then
I see a range on slider
Then
I seek video to
"16"
seconds
And
I click video button
"pause"
And
I reload the page
And
I click video button
"play"
Then I see video starts playing from "0
:
16"
position
# 13
Scenario
:
Check that position is stored on page refresh, position before start-end range
Given
I have created a Video component with subtitles
And
Make sure captions are closed
And
I edit the component
And
I open tab
"Advanced"
And I set value "00
:
00
:
12"
to
the
field
"Start
Time"
And I set value "00
:
00
:
24"
to
the
field
"End
Time"
And
I save changes
And
I click video button
"play"
Then
I see a range on slider
Then
I seek video to
"5"
seconds
And
I click video button
"pause"
And
I reload the page
And
I click video button
"play"
Then I see video starts playing from "0
:
12"
position
# 14
Scenario
:
Check that position is stored on page refresh, position after start-end range
Given
I have created a Video component with subtitles
And
Make sure captions are closed
And
I edit the component
And
I open tab
"Advanced"
And I set value "00
:
00
:
12"
to
the
field
"Start
Time"
And I set value "00
:
00
:
24"
to
the
field
"End
Time"
And
I save changes
And
I click video button
"play"
Then
I see a range on slider
Then
I seek video to
"30"
seconds
And
I click video button
"pause"
And
I reload the page
And
I click video button
"play"
Then I see video starts playing from "0
:
12"
position
cms/djangoapps/contentstore/features/video.py
View file @
d8144b0d
...
@@ -198,3 +198,17 @@ def click_button_video(_step, button_type):
...
@@ -198,3 +198,17 @@ def click_button_video(_step, button_type):
button
=
button_type
.
strip
()
button
=
button_type
.
strip
()
world
.
css_click
(
VIDEO_BUTTONS
[
button
])
world
.
css_click
(
VIDEO_BUTTONS
[
button
])
@step
(
'I seek video to "([^"]*)" seconds$'
)
def
seek_video_to_n_seconds
(
_step
,
seconds
):
time
=
float
(
seconds
.
strip
())
jsCode
=
"$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0:f}}})"
.
format
(
time
)
world
.
browser
.
execute_script
(
jsCode
)
@step
(
'I see video starts playing from "([^"]*)" position$'
)
def
start_playing_video_from_n_seconds
(
_step
,
position
):
world
.
wait_for
(
func
=
lambda
_
:
world
.
css_html
(
'.vidtime'
)[:
4
]
==
position
.
strip
(),
timeout
=
5
)
common/lib/xmodule/xmodule/fields.py
View file @
d8144b0d
...
@@ -140,7 +140,8 @@ class RelativeTime(Field):
...
@@ -140,7 +140,8 @@ class RelativeTime(Field):
# Timedeltas are immutable, see http://docs.python.org/2/library/datetime.html#available-types
# Timedeltas are immutable, see http://docs.python.org/2/library/datetime.html#available-types
MUTABLE
=
False
MUTABLE
=
False
def
_isotime_to_timedelta
(
self
,
value
):
@classmethod
def
isotime_to_timedelta
(
cls
,
value
):
"""
"""
Validate that value in "HH:MM:SS" format and convert to timedelta.
Validate that value in "HH:MM:SS" format and convert to timedelta.
...
@@ -175,7 +176,7 @@ class RelativeTime(Field):
...
@@ -175,7 +176,7 @@ class RelativeTime(Field):
return
datetime
.
timedelta
(
seconds
=
value
)
return
datetime
.
timedelta
(
seconds
=
value
)
if
isinstance
(
value
,
basestring
):
if
isinstance
(
value
,
basestring
):
return
self
.
_
isotime_to_timedelta
(
value
)
return
self
.
isotime_to_timedelta
(
value
)
msg
=
"RelativeTime Field {0} has bad value '{1!r}'"
.
format
(
self
.
_name
,
value
)
msg
=
"RelativeTime Field {0} has bad value '{1!r}'"
.
format
(
self
.
_name
,
value
)
raise
TypeError
(
msg
)
raise
TypeError
(
msg
)
...
...
common/lib/xmodule/xmodule/js/fixtures/video.html
View file @
d8144b0d
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
data-speed=
"1.5"
data-speed=
"1.5"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
data-saved-video-position=
"0"
data-caption-asset-path=
"/static/subs/"
data-caption-asset-path=
"/static/subs/"
data-autoplay=
"False"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-timeout=
"1500"
...
...
common/lib/xmodule/xmodule/js/fixtures/video_all.html
View file @
d8144b0d
...
@@ -9,6 +9,7 @@
...
@@ -9,6 +9,7 @@
data-speed=
"1.5"
data-speed=
"1.5"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
data-saved-video-position=
"0"
data-caption-asset-path=
"/static/subs/"
data-caption-asset-path=
"/static/subs/"
data-sub=
"Z5KLxerq05Y"
data-sub=
"Z5KLxerq05Y"
data-mp4-source=
"xmodule/include/fixtures/test.mp4"
data-mp4-source=
"xmodule/include/fixtures/test.mp4"
...
...
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
View file @
d8144b0d
...
@@ -9,6 +9,7 @@
...
@@ -9,6 +9,7 @@
data-speed=
"1.5"
data-speed=
"1.5"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
data-saved-video-position=
"0"
data-caption-asset-path=
"/static/subs/"
data-caption-asset-path=
"/static/subs/"
data-sub=
"Z5KLxerq05Y"
data-sub=
"Z5KLxerq05Y"
data-mp4-source=
"xmodule/include/fixtures/test.mp4"
data-mp4-source=
"xmodule/include/fixtures/test.mp4"
...
...
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
View file @
d8144b0d
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
data-speed=
"1.5"
data-speed=
"1.5"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
data-saved-video-position=
"0"
data-caption-asset-path=
"/static/subs/"
data-caption-asset-path=
"/static/subs/"
data-autoplay=
"False"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-timeout=
"1500"
...
...
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
View file @
d8144b0d
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
data-speed=
"1.5"
data-speed=
"1.5"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
data-saved-video-position=
"0"
data-caption-asset-path=
"/static/subs/"
data-caption-asset-path=
"/static/subs/"
data-autoplay=
"False"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-timeout=
"1500"
...
...
common/lib/xmodule/xmodule/js/spec/helper.js
View file @
d8144b0d
...
@@ -207,18 +207,17 @@
...
@@ -207,18 +207,17 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
addMatchers
({
this
.
addMatchers
({
toHaveAttrs
:
function
(
attrs
)
{
toHaveAttrs
:
function
(
attrs
)
{
var
element
=
this
.
actual
,
var
element
;
result
=
true
;
if
(
$
.
isEmptyObject
(
attrs
))
{
if
(
$
.
isEmptyObject
(
attrs
))
{
return
false
;
return
false
;
}
}
$
.
each
(
attrs
,
function
(
name
,
value
)
{
element
=
this
.
actual
;
return
result
=
result
&&
element
.
attr
(
name
)
===
value
;
});
return
result
;
return
_
.
every
(
attrs
,
function
(
value
,
name
)
{
return
element
.
attr
(
name
)
===
value
;
});
},
},
toBeInRange
:
function
(
min
,
max
)
{
toBeInRange
:
function
(
min
,
max
)
{
return
min
<=
this
.
actual
&&
this
.
actual
<=
max
;
return
min
<=
this
.
actual
&&
this
.
actual
<=
max
;
...
...
common/lib/xmodule/xmodule/js/spec/video/cookie_storage_spec.js
deleted
100644 → 0
View file @
0b3604f8
(
function
(
requirejs
,
require
,
define
)
{
require
(
[
'video/00_cookie_storage.js'
],
function
(
CookieStorage
)
{
describe
(
'CookieStorage'
,
function
()
{
var
mostRecentCall
;
beforeEach
(
function
()
{
mostRecentCall
=
$
.
cookie
.
mostRecentCall
;
});
afterEach
(
function
()
{
CookieStorage
(
'test_storage'
).
clear
();
});
describe
(
'intialize'
,
function
()
{
it
(
'with namespace'
,
function
()
{
var
storage
=
CookieStorage
(
'test_storage'
);
storage
.
setItem
(
'item_1'
,
'value_1'
);
expect
(
mostRecentCall
.
args
[
0
]).
toBe
(
'test_storage'
);
});
it
(
'without namespace'
,
function
()
{
var
storage
=
CookieStorage
();
storage
.
setItem
(
'item_1'
,
'value_1'
);
expect
(
mostRecentCall
.
args
[
0
]).
toBe
(
'cookieStorage'
);
});
});
it
(
'unload'
,
function
()
{
var
expected
=
JSON
.
stringify
({
storage
:
{
item_2
:
{
value
:
'value_2'
,
session
:
false
}
},
keys
:
[
'item_2'
]
}),
storage
=
CookieStorage
(
'test_storage'
);
storage
.
setItem
(
'item_1'
,
'value_1'
,
true
);
storage
.
setItem
(
'item_2'
,
'value_2'
);
$
(
window
).
trigger
(
'unload'
);
expect
(
mostRecentCall
.
args
[
1
]).
toBe
(
expected
);
});
describe
(
'methods: '
,
function
()
{
var
data
=
{
storage
:
{
item_1
:
{
value
:
'value_1'
,
session
:
false
}
},
keys
:
[
'item_1'
]
},
storage
;
beforeEach
(
function
()
{
$
.
cookie
.
andReturn
(
JSON
.
stringify
(
data
));
storage
=
CookieStorage
(
'test_storage'
);
});
describe
(
'setItem'
,
function
()
{
it
(
'pass correct data'
,
function
()
{
var
expected
=
JSON
.
stringify
({
storage
:
{
item_1
:
{
value
:
'value_1'
,
session
:
false
},
item_2
:
{
value
:
'value_2'
,
session
:
false
},
item_3
:
{
value
:
'value_3'
,
session
:
true
},
},
keys
:
[
'item_1'
,
'item_2'
,
'item_3'
]
});
storage
.
setItem
(
'item_2'
,
'value_2'
);
storage
.
setItem
(
'item_3'
,
'value_3'
,
true
);
expect
(
mostRecentCall
.
args
[
0
]).
toBe
(
'test_storage'
);
expect
(
mostRecentCall
.
args
[
1
]).
toBe
(
expected
);
});
it
(
'pass broken arguments'
,
function
()
{
$
.
cookie
.
reset
();
storage
.
setItem
(
null
,
'value_1'
);
expect
(
$
.
cookie
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'getItem'
,
function
()
{
it
(
'item exist'
,
function
()
{
$
.
each
(
data
[
'storage'
],
function
(
key
,
value
)
{
expect
(
storage
.
getItem
(
key
)).
toBe
(
value
[
'value'
]);
});
});
it
(
'item does not exist'
,
function
()
{
expect
(
storage
.
getItem
(
'nonexistent'
)).
toBe
(
null
);
});
});
describe
(
'removeItem'
,
function
()
{
it
(
'item exist'
,
function
()
{
var
expected
=
JSON
.
stringify
({
storage
:
{},
keys
:
[]
});
storage
.
removeItem
(
'item_1'
);
expect
(
mostRecentCall
.
args
[
1
]).
toBe
(
expected
);
});
it
(
'item does not exist'
,
function
()
{
storage
.
removeItem
(
'nonexistent'
);
expect
(
mostRecentCall
.
args
[
1
]).
toBe
(
JSON
.
stringify
(
data
));
});
});
it
(
'clear'
,
function
()
{
storage
.
clear
();
expect
(
mostRecentCall
.
args
[
1
]).
toBe
(
null
);
});
describe
(
'key'
,
function
()
{
it
(
'key exist'
,
function
()
{
$
.
each
(
data
[
'keys'
],
function
(
index
,
name
)
{
expect
(
storage
.
key
(
index
)).
toBe
(
name
);
});
});
it
(
'key is grater than keys list'
,
function
()
{
expect
(
storage
.
key
(
100
)).
toBe
(
null
);
});
});
});
});
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/spec/video/events_spec.js
View file @
d8144b0d
...
@@ -19,6 +19,7 @@
...
@@ -19,6 +19,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
it
(
'initialize'
,
function
()
{
it
(
'initialize'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
View file @
d8144b0d
...
@@ -8,6 +8,8 @@
...
@@ -8,6 +8,8 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
VideoState
=
{};
window
.
VideoState
.
id
=
{};
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
@@ -80,7 +82,7 @@
...
@@ -80,7 +82,7 @@
'0.75'
:
sub
,
'0.75'
:
sub
,
'1.0'
:
sub
,
'1.0'
:
sub
,
'1.25'
:
sub
,
'1.25'
:
sub
,
'1.5'
:
sub
'1.5
0
'
:
sub
});
});
});
});
...
@@ -97,7 +99,7 @@
...
@@ -97,7 +99,7 @@
'0.75'
:
sub
,
'0.75'
:
sub
,
'1.0'
:
sub
,
'1.0'
:
sub
,
'1.25'
:
sub
,
'1.25'
:
sub
,
'1.5'
:
sub
'1.5
0
'
:
sub
});
});
});
});
...
@@ -227,10 +229,17 @@
...
@@ -227,10 +229,17 @@
expect
(
state
.
videoPlayer
.
skipOnEndedStartEndReset
).
toBe
(
true
);
expect
(
state
.
videoPlayer
.
skipOnEndedStartEndReset
).
toBe
(
true
);
});
});
it
(
'when position is not 0: cue is called with stored position value'
,
function
()
{
state
.
config
.
savedVideoPosition
=
15
;
state
.
videoPlayer
.
updatePlayTime
(
10
);
expect
(
state
.
videoPlayer
.
player
.
cueVideoById
).
toHaveBeenCalledWith
(
'cogebirgzzM'
,
15
);
});
it
(
'Handling cue state'
,
function
()
{
it
(
'Handling cue state'
,
function
()
{
spyOn
(
state
.
videoPlayer
,
'play'
);
spyOn
(
state
.
videoPlayer
,
'play'
);
state
.
videoPlayer
.
s
tartTime
=
10
;
state
.
videoPlayer
.
s
eekToTimeOnCued
=
10
;
state
.
videoPlayer
.
onStateChange
({
data
:
5
});
state
.
videoPlayer
.
onStateChange
({
data
:
5
});
expect
(
state
.
videoPlayer
.
player
.
seekTo
).
toHaveBeenCalledWith
(
10
,
true
);
expect
(
state
.
videoPlayer
.
player
.
seekTo
).
toHaveBeenCalledWith
(
10
,
true
);
...
@@ -397,7 +406,7 @@
...
@@ -397,7 +406,7 @@
it
(
'save setting for new speed'
,
function
()
{
it
(
'save setting for new speed'
,
function
()
{
expect
(
state
.
storage
.
getItem
(
'general_speed'
)).
toBe
(
'0.75'
);
expect
(
state
.
storage
.
getItem
(
'general_speed'
)).
toBe
(
'0.75'
);
expect
(
state
.
storage
.
getItem
(
'
video_speed_'
+
state
.
id
)).
toBe
(
'0.75'
);
expect
(
state
.
storage
.
getItem
(
'
speed'
,
true
)).
toBe
(
'0.75'
);
});
});
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
View file @
d8144b0d
...
@@ -9,7 +9,7 @@
...
@@ -9,7 +9,7 @@
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
state
=
undefined
;
state
.
storage
.
clear
()
;
$
.
fn
.
scrollTo
.
reset
();
$
.
fn
.
scrollTo
.
reset
();
$
(
'.subtitles'
).
remove
();
$
(
'.subtitles'
).
remove
();
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
...
...
common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js
0 → 100644
View file @
d8144b0d
(
function
(
requirejs
,
require
,
define
,
undefined
)
{
'use strict'
;
require
(
[
'video/01_initialize.js'
],
function
(
Initialize
)
{
describe
(
'Initialize'
,
function
()
{
describe
(
'saveState function'
,
function
()
{
var
state
,
videoPlayerCurrentTime
,
newCurrentTime
,
speed
;
// We make sure that `currentTime` is a float. We need to test
// that Math.round() is called.
videoPlayerCurrentTime
=
3.1242
;
// We have two times, because one is stored in
// `videoPlayer.currentTime`, and the other is passed directly to
// `saveState` in `data` object. In each case, there is different
// code that handles these times. They have to be different for
// test completeness sake. Also, make sure it is float, as is the
// time above.
newCurrentTime
=
5.4
;
speed
=
'0.75'
;
beforeEach
(
function
()
{
state
=
{
videoPlayer
:
{
currentTime
:
videoPlayerCurrentTime
},
storage
:
{
setItem
:
jasmine
.
createSpy
()
},
config
:
{
saveStateUrl
:
'http://example.com/save_user_state'
}
};
spyOn
(
$
,
'ajax'
);
spyOn
(
Time
,
'formatFull'
).
andCallThrough
();
});
afterEach
(
function
()
{
state
=
undefined
;
});
it
(
'data is not an object, async is true'
,
function
()
{
itSpec
({
asyncVal
:
true
,
speedVal
:
undefined
,
positionVal
:
videoPlayerCurrentTime
,
data
:
undefined
,
ajaxData
:
{
saved_video_position
:
Time
.
formatFull
(
Math
.
round
(
videoPlayerCurrentTime
))
}
});
});
it
(
'data contains speed, async is false'
,
function
()
{
itSpec
({
asyncVal
:
false
,
speedVal
:
speed
,
positionVal
:
undefined
,
data
:
{
speed
:
speed
},
ajaxData
:
{
speed
:
speed
}
});
});
it
(
'data contains float position, async is true'
,
function
()
{
itSpec
({
asyncVal
:
true
,
speedVal
:
undefined
,
positionVal
:
newCurrentTime
,
data
:
{
saved_video_position
:
newCurrentTime
},
ajaxData
:
{
saved_video_position
:
Time
.
formatFull
(
Math
.
round
(
newCurrentTime
))
}
});
});
it
(
'data contains speed and rounded position, async is false'
,
function
()
{
itSpec
({
asyncVal
:
false
,
speedVal
:
speed
,
positionVal
:
Math
.
round
(
newCurrentTime
),
data
:
{
speed
:
speed
,
saved_video_position
:
Math
.
round
(
newCurrentTime
)
},
ajaxData
:
{
speed
:
speed
,
saved_video_position
:
Time
.
formatFull
(
Math
.
round
(
newCurrentTime
))
}
});
});
it
(
'data contains empty object, async is true'
,
function
()
{
itSpec
({
asyncVal
:
true
,
speedVal
:
undefined
,
positionVal
:
undefined
,
data
:
{},
ajaxData
:
{}
});
});
return
;
function
itSpec
(
value
)
{
var
asyncVal
=
value
.
asyncVal
,
speedVal
=
value
.
speedVal
,
positionVal
=
value
.
positionVal
,
data
=
value
.
data
,
ajaxData
=
value
.
ajaxData
;
Initialize
.
prototype
.
saveState
.
call
(
state
,
asyncVal
,
data
);
if
(
speedVal
)
{
expect
(
state
.
storage
.
setItem
).
toHaveBeenCalledWith
(
'speed'
,
speedVal
,
true
);
}
if
(
positionVal
)
{
expect
(
state
.
storage
.
setItem
).
toHaveBeenCalledWith
(
'savedVideoPosition'
,
positionVal
,
true
);
expect
(
Time
.
formatFull
).
toHaveBeenCalledWith
(
positionVal
);
}
expect
(
$
.
ajax
).
toHaveBeenCalledWith
({
url
:
state
.
config
.
saveStateUrl
,
type
:
'POST'
,
async
:
asyncVal
,
dataType
:
'json'
,
data
:
ajaxData
});
}
});
});
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
View file @
d8144b0d
...
@@ -8,10 +8,7 @@
...
@@ -8,10 +8,7 @@
.
andReturn
(
null
);
.
andReturn
(
null
);
state
=
jasmine
.
initializePlayer
();
state
=
jasmine
.
initializePlayer
();
videoControl
=
state
.
videoControl
;
videoControl
=
state
.
videoControl
;
$
.
fn
.
scrollTo
.
reset
();
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -21,6 +18,8 @@
...
@@ -21,6 +18,8 @@
// had before. Removing of `source` tag, not `video` tag, stops
// had before. Removing of `source` tag, not `video` tag, stops
// loading video source and clears the memory.
// loading video source and clears the memory.
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
$
.
fn
.
scrollTo
.
reset
();
state
.
storage
.
clear
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_control_spec.js
View file @
d8144b0d
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
state
.
storage
.
clear
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
d8144b0d
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
@@ -213,10 +214,6 @@
...
@@ -213,10 +214,6 @@
);
);
});
});
it
(
'pause other video player'
,
function
()
{
expect
(
oldState
.
videoPlayer
.
onPause
).
toHaveBeenCalled
();
});
it
(
'set update interval'
,
function
()
{
it
(
'set update interval'
,
function
()
{
expect
(
window
.
setInterval
).
toHaveBeenCalledWith
(
expect
(
window
.
setInterval
).
toHaveBeenCalledWith
(
state
.
videoPlayer
.
update
,
200
state
.
videoPlayer
.
update
,
200
...
...
common/lib/xmodule/xmodule/js/spec/video/video_progress_slider_spec.js
View file @
d8144b0d
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js
View file @
d8144b0d
...
@@ -12,6 +12,7 @@
...
@@ -12,6 +12,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
@@ -20,9 +21,15 @@
...
@@ -20,9 +21,15 @@
});
});
it
(
'render the quality control'
,
function
()
{
it
(
'render the quality control'
,
function
()
{
var
container
=
state
.
videoControl
.
secondaryControlsEl
;
waitsFor
(
function
()
{
return
state
.
videoControl
;
},
'videoControl is present'
,
5000
);
expect
(
container
).
toContain
(
'a.quality_control'
);
runs
(
function
()
{
var
container
=
state
.
videoControl
.
secondaryControlsEl
;
expect
(
container
).
toContain
(
'a.quality_control'
);
});
});
});
it
(
'add ARIA attributes to quality control'
,
function
()
{
it
(
'add ARIA attributes to quality control'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
View file @
d8144b0d
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_storage_spec.js
0 → 100644
View file @
d8144b0d
(
function
(
requirejs
,
require
,
define
,
undefined
)
{
require
(
[
'video/00_video_storage.js'
],
function
(
VideoStorage
)
{
describe
(
'VideoStorage'
,
function
()
{
var
namespace
=
'test_storage'
,
id
=
'video_id'
;
afterEach
(
function
()
{
VideoStorage
(
namespace
,
id
).
clear
();
});
describe
(
'initialize'
,
function
()
{
it
(
'with namespace and id'
,
function
()
{
var
storage
=
VideoStorage
(
namespace
,
id
);
expect
(
window
[
namespace
]).
toBeDefined
();
expect
(
window
[
namespace
][
id
]).
toBeDefined
();
});
it
(
'without namespace and id'
,
function
()
{
spyOn
(
Number
.
prototype
,
'toString'
).
andReturn
(
'0.abcdedg'
);
var
storage
=
VideoStorage
();
expect
(
window
.
VideoStorage
).
toBeDefined
();
expect
(
window
.
VideoStorage
.
abcdedg
).
toBeDefined
();
});
});
describe
(
'methods: '
,
function
()
{
var
data
,
storage
;
beforeEach
(
function
()
{
data
=
{
item_2
:
'value_2'
};
data
[
id
]
=
{
item_1
:
'value_1'
};
window
[
namespace
]
=
data
;
storage
=
VideoStorage
(
namespace
,
id
);
});
it
(
'setItem'
,
function
()
{
var
expected
=
$
.
extend
(
true
,
{},
data
,
{
item_4
:
'value_4'
});
expected
[
id
][
'item_3'
]
=
'value_3'
;
storage
.
setItem
(
'item_3'
,
'value_3'
,
true
);
storage
.
setItem
(
'item_4'
,
'value_4'
);
expect
(
window
[
namespace
]).
toEqual
(
expected
);
});
it
(
'getItem'
,
function
()
{
var
data
=
window
[
namespace
],
getItem
=
storage
.
getItem
;
expect
(
getItem
(
'item_1'
,
true
)).
toBe
(
data
[
id
][
'item_1'
]);
expect
(
getItem
(
'item_2'
)).
toBe
(
data
[
'item_2'
]);
expect
(
getItem
(
'item_3'
)).
toBeUndefined
();
});
it
(
'removeItem'
,
function
()
{
var
data
=
window
[
namespace
],
removeItem
=
storage
.
removeItem
;
removeItem
(
'item_1'
,
true
);
removeItem
(
'item_2'
);
expect
(
data
[
id
][
'item_1'
]).
toBeUndefined
();
expect
(
data
[
'item_2'
]).
toBeUndefined
();
});
it
(
'clear'
,
function
()
{
var
expected
=
{};
expected
[
id
]
=
{};
storage
.
clear
();
expect
(
window
[
namespace
]).
toEqual
(
expected
);
});
});
});
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
View file @
d8144b0d
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
afterEach
(
function
()
{
afterEach
(
function
()
{
$
(
'source'
).
remove
();
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
});
});
describe
(
'constructor'
,
function
()
{
describe
(
'constructor'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/src/time.coffee
View file @
d8144b0d
...
@@ -13,5 +13,18 @@ class @Time
...
@@ -13,5 +13,18 @@ class @Time
else
else
"
#{
minutes
}
:
#{
pad
(
seconds
%
60
)
}
"
"
#{
minutes
}
:
#{
pad
(
seconds
%
60
)
}
"
@
formatFull
:
(
time
)
->
pad
=
(
number
)
->
if
number
<
10
then
"0
#{
number
}
"
else
number
seconds
=
Math
.
floor
time
minutes
=
Math
.
floor
seconds
/
60
hours
=
Math
.
floor
minutes
/
60
seconds
=
seconds
%
60
minutes
=
minutes
%
60
# The returned value will not be user-facing. So no need for
# internationalization.
"
#{
pad
(
hours
)
}
:
#{
pad
(
minutes
)
}
:
#{
pad
(
seconds
%
60
)
}
"
@
convert
:
(
time
,
oldSpeed
,
newSpeed
)
->
@
convert
:
(
time
,
oldSpeed
,
newSpeed
)
->
(
time
*
oldSpeed
/
newSpeed
).
toFixed
(
3
)
(
time
*
oldSpeed
/
newSpeed
).
toFixed
(
3
)
common/lib/xmodule/xmodule/js/src/video/00_cookie_storage.js
deleted
100644 → 0
View file @
0b3604f8
(
function
(
requirejs
,
require
,
define
)
{
define
(
'video/00_cookie_storage.js'
,
[],
function
()
{
"use strict"
;
/**
* Provides convenient way to work with cookies.
*
* Maximum 4096 bytes can be stored per namespace.
*
* @TODO: Uses localStorage if available.
*
* @param {string} namespace Namespace that is used to store data.
* @return {object} CookieStorage API.
*/
var
CookieStorage
=
function
(
namespace
)
{
var
Storage
;
/**
* Returns an empty storage with proper data structure.
*
* @private
* @return {object} Empty storage.
*/
var
_getEmptyStorage
=
function
()
{
return
{
storage
:
{},
keys
:
[]
};
};
/**
* Returns the current value associated with the given namespace.
* If data doesn't exist or has data with incorrect interface, it creates
* an empty storage with proper data structure.
*
* @private
* @param {string} namespace Namespace that is used to store data.
* @return {object} Stored data or an empty storage.
*/
var
_getData
=
function
(
namespace
)
{
var
data
;
try
{
data
=
JSON
.
parse
(
$
.
cookie
(
namespace
));
}
catch
(
err
)
{
}
if
(
!
data
||
!
data
[
'storage'
]
||
!
data
[
'keys'
])
{
return
_getEmptyStorage
();
}
return
data
;
};
/**
* Clears cookies that has flag `session` equals true.
*
* @private
*/
var
_clearSession
=
function
()
{
Storage
[
'keys'
]
=
$
.
grep
(
Storage
[
'keys'
],
function
(
key_name
,
index
)
{
if
(
Storage
[
'storage'
][
key_name
][
'session'
])
{
delete
Storage
[
'storage'
][
key_name
];
return
false
;
}
return
true
;
});
$
.
cookie
(
namespace
,
JSON
.
stringify
(
Storage
),
{
expires
:
3650
,
path
:
'/'
});
};
/**
* Adds new value to the storage or rewrites existent.
*
* @param {string} name Identifier of the data.
* @param {any} value Data to store.
* @param {boolean} useSession Data with this flag will be removed on
* window unload.
*/
var
setItem
=
function
(
name
,
value
,
useSession
)
{
if
(
name
)
{
if
(
$
.
inArray
(
name
,
Storage
[
'keys'
])
===
-
1
)
{
Storage
[
'keys'
].
push
(
name
);
}
Storage
[
'storage'
][
name
]
=
{
value
:
value
,
session
:
useSession
?
true
:
false
};
$
.
cookie
(
namespace
,
JSON
.
stringify
(
Storage
),
{
expires
:
3650
,
path
:
'/'
});
}
};
/**
* Returns the current value associated with the given name.
*
* @param {string} name Identifier of the data.
* @return {any} The current value associated with the given name.
* If the given key does not exist in the list
* associated with the object then this method must return null.
*/
var
getItem
=
function
(
name
)
{
try
{
return
Storage
[
'storage'
][
name
][
'value'
];
}
catch
(
err
)
{
}
return
null
;
};
/**
* Removes the current value associated with the given name.
*
* @param {string} name Identifier of the data.
*/
var
removeItem
=
function
(
name
)
{
delete
Storage
[
'storage'
][
name
];
Storage
[
'keys'
]
=
$
.
grep
(
Storage
[
'keys'
],
function
(
key_name
,
index
)
{
return
name
!==
key_name
;
});
$
.
cookie
(
namespace
,
JSON
.
stringify
(
Storage
),
{
expires
:
3650
,
path
:
'/'
});
};
/**
* Empties the storage.
*
*/
var
clear
=
function
()
{
Storage
=
_getEmptyStorage
();
$
.
cookie
(
namespace
,
null
,
{
expires
:
-
1
,
path
:
'/'
});
};
/**
* Returns the name of the `n`th key in the list.
*
* @param {number} n Index of the key.
* @return {string} Name of the `n`th key in the list.
* If `n` is greater than or equal to the number of key/value pairs
* in the object, then this method must return `null`.
*/
var
key
=
function
(
n
)
{
if
(
n
>=
Storage
[
'keys'
].
length
)
{
return
null
;
}
return
Storage
[
'keys'
][
n
];
};
/**
* Initializes the module: creates a storage with proper namespace, binds
* `unload` event.
*
* @private
*/
(
function
initialize
()
{
if
(
!
namespace
)
{
namespace
=
'cookieStorage'
;
}
Storage
=
_getData
(
namespace
);
$
(
window
).
unload
(
_clearSession
);
}());
return
{
clear
:
clear
,
getItem
:
getItem
,
key
:
key
,
removeItem
:
removeItem
,
setItem
:
setItem
};
};
return
CookieStorage
;
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/00_video_storage.js
0 → 100644
View file @
d8144b0d
(
function
(
requirejs
,
require
,
define
)
{
define
(
'video/00_video_storage.js'
,
[],
function
()
{
"use strict"
;
/**
* Provides convenient way to store key value pairs.
*
* @param {string} namespace Namespace that is used to store data.
* @return {object} VideoStorage API.
*/
var
VideoStorage
=
function
(
namespace
,
id
)
{
/**
* Adds new value to the storage or rewrites existent.
*
* @param {string} name Identifier of the data.
* @param {any} value Data to store.
* @param {boolean} instanceSpecific Data with this flag will be added
* to instance specific storage.
*/
var
setItem
=
function
(
name
,
value
,
instanceSpecific
)
{
if
(
name
)
{
if
(
instanceSpecific
)
{
window
[
namespace
][
id
][
name
]
=
value
;
}
else
{
window
[
namespace
][
name
]
=
value
;
}
}
};
/**
* Returns the current value associated with the given name.
*
* @param {string} name Identifier of the data.
* @param {boolean} instanceSpecific Data with this flag will be added
* to instance specific storage.
* @return {any} The current value associated with the given name.
* If the given key does not exist in the list
* associated with the object then this method must return null.
*/
var
getItem
=
function
(
name
,
instanceSpecific
)
{
if
(
instanceSpecific
)
{
return
window
[
namespace
][
id
][
name
];
}
else
{
return
window
[
namespace
][
name
];
}
};
/**
* Removes the current value associated with the given name.
*
* @param {string} name Identifier of the data.
* @param {boolean} instanceSpecific Data with this flag will be added
* to instance specific storage.
*/
var
removeItem
=
function
(
name
,
instanceSpecific
)
{
if
(
instanceSpecific
)
{
delete
window
[
namespace
][
id
][
name
];
}
else
{
delete
window
[
namespace
][
name
];
}
};
/**
* Empties the storage.
*
*/
var
clear
=
function
()
{
window
[
namespace
]
=
{};
window
[
namespace
][
id
]
=
{};
};
/**
* Initializes the module: creates a storage with proper namespace.
*
* @private
*/
(
function
initialize
()
{
if
(
!
namespace
)
{
namespace
=
'VideoStorage'
;
}
if
(
!
id
)
{
// Generate random alpha-numeric string.
id
=
Math
.
random
().
toString
(
36
).
slice
(
2
);
}
window
[
namespace
]
=
window
[
namespace
]
||
{};
window
[
namespace
][
id
]
=
window
[
namespace
][
id
]
||
{};
}());
return
{
clear
:
clear
,
getItem
:
getItem
,
removeItem
:
removeItem
,
setItem
:
setItem
};
};
return
VideoStorage
;
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
d8144b0d
...
@@ -14,8 +14,8 @@
...
@@ -14,8 +14,8 @@
define
(
define
(
'video/01_initialize.js'
,
'video/01_initialize.js'
,
[
'video/03_video_player.js'
,
'video/00_
cookie
_storage.js'
],
[
'video/03_video_player.js'
,
'video/00_
video
_storage.js'
],
function
(
VideoPlayer
,
Cookie
Storage
)
{
function
(
VideoPlayer
,
Video
Storage
)
{
/**
/**
* @function
* @function
*
*
...
@@ -26,7 +26,7 @@ function (VideoPlayer, CookieStorage) {
...
@@ -26,7 +26,7 @@ function (VideoPlayer, CookieStorage) {
* available via this object.
* available via this object.
* @param {DOM element} element Container of the entire Video DOM element.
* @param {DOM element} element Container of the entire Video DOM element.
*/
*/
return
function
(
state
,
element
)
{
var
Initialize
=
function
(
state
,
element
)
{
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
state
.
initialize
(
element
)
state
.
initialize
(
element
)
...
@@ -56,8 +56,26 @@ function (VideoPlayer, CookieStorage) {
...
@@ -56,8 +56,26 @@ function (VideoPlayer, CookieStorage) {
state
.
el
.
trigger
(
'initialize'
,
arguments
);
state
.
el
.
trigger
(
'initialize'
,
arguments
);
});
});
});
});
},
methodsDict
=
{
bindTo
:
bindTo
,
fetchMetadata
:
fetchMetadata
,
getDuration
:
getDuration
,
getVideoMetadata
:
getVideoMetadata
,
initialize
:
initialize
,
parseSpeed
:
parseSpeed
,
parseVideoSources
:
parseVideoSources
,
parseYoutubeStreams
:
parseYoutubeStreams
,
saveState
:
saveState
,
setSpeed
:
setSpeed
,
trigger
:
trigger
,
youtubeId
:
youtubeId
};
};
Initialize
.
prototype
=
methodsDict
;
return
Initialize
;
// ***************************************************************
// ***************************************************************
// Private functions start here. Private functions start with underscore.
// Private functions start here. Private functions start with underscore.
// ***************************************************************
// ***************************************************************
...
@@ -73,21 +91,6 @@ function (VideoPlayer, CookieStorage) {
...
@@ -73,21 +91,6 @@ function (VideoPlayer, CookieStorage) {
* methods, modules) of the Video player.
* methods, modules) of the Video player.
*/
*/
function
_makeFunctionsPublic
(
state
)
{
function
_makeFunctionsPublic
(
state
)
{
var
methodsDict
=
{
bindTo
:
bindTo
,
fetchMetadata
:
fetchMetadata
,
getDuration
:
getDuration
,
getVideoMetadata
:
getVideoMetadata
,
initialize
:
initialize
,
parseSpeed
:
parseSpeed
,
parseVideoSources
:
parseVideoSources
,
parseYoutubeStreams
:
parseYoutubeStreams
,
setSpeed
:
setSpeed
,
stopBuffering
:
stopBuffering
,
trigger
:
trigger
,
youtubeId
:
youtubeId
};
bindTo
(
methodsDict
,
state
,
state
);
bindTo
(
methodsDict
,
state
,
state
);
}
}
...
@@ -201,7 +204,7 @@ function (VideoPlayer, CookieStorage) {
...
@@ -201,7 +204,7 @@ function (VideoPlayer, CookieStorage) {
'0.75'
:
state
.
config
.
sub
,
'0.75'
:
state
.
config
.
sub
,
'1.0'
:
state
.
config
.
sub
,
'1.0'
:
state
.
config
.
sub
,
'1.25'
:
state
.
config
.
sub
,
'1.25'
:
state
.
config
.
sub
,
'1.5'
:
state
.
config
.
sub
'1.5
0
'
:
state
.
config
.
sub
};
};
// We must have at least one non-YouTube video source available.
// We must have at least one non-YouTube video source available.
...
@@ -271,13 +274,13 @@ function (VideoPlayer, CookieStorage) {
...
@@ -271,13 +274,13 @@ function (VideoPlayer, CookieStorage) {
return
dfd
.
promise
();
return
dfd
.
promise
();
}
}
function
_getConfiguration
(
data
)
{
function
_getConfiguration
(
data
,
storage
)
{
var
isBoolean
=
function
(
value
)
{
var
isBoolean
=
function
(
value
)
{
var
regExp
=
/^true$/i
;
var
regExp
=
/^true$/i
;
return
regExp
.
test
(
value
.
toString
());
return
regExp
.
test
(
value
.
toString
());
},
},
// List of keys that will be extracted form the configuration.
// List of keys that will be extracted form the configuration.
extractKeys
=
[
'speed'
],
extractKeys
=
[],
// Compatibility keys used to change names of some parameters in
// Compatibility keys used to change names of some parameters in
// the final configuration.
// the final configuration.
compatKeys
=
{
compatKeys
=
{
...
@@ -289,6 +292,19 @@ function (VideoPlayer, CookieStorage) {
...
@@ -289,6 +292,19 @@ function (VideoPlayer, CookieStorage) {
'showCaptions'
:
isBoolean
,
'showCaptions'
:
isBoolean
,
'autoplay'
:
isBoolean
,
'autoplay'
:
isBoolean
,
'autohideHtml5'
:
isBoolean
,
'autohideHtml5'
:
isBoolean
,
'savedVideoPosition'
:
function
(
value
)
{
return
storage
.
getItem
(
'savedVideoPosition'
,
true
)
||
Number
(
value
)
||
0
;
},
'speed'
:
function
(
value
)
{
return
storage
.
getItem
(
'speed'
,
true
)
||
value
;
},
'generalSpeed'
:
function
(
value
)
{
return
storage
.
getItem
(
'general_speed'
)
||
value
||
'1.0'
;
},
'ytTestTimeout'
:
function
(
value
)
{
'ytTestTimeout'
:
function
(
value
)
{
value
=
parseInt
(
value
,
10
);
value
=
parseInt
(
value
,
10
);
...
@@ -380,12 +396,7 @@ function (VideoPlayer, CookieStorage) {
...
@@ -380,12 +396,7 @@ function (VideoPlayer, CookieStorage) {
id
=
el
.
attr
(
'id'
).
replace
(
/video_/
,
''
),
id
=
el
.
attr
(
'id'
).
replace
(
/video_/
,
''
),
__dfd__
=
$
.
Deferred
(),
__dfd__
=
$
.
Deferred
(),
isTouch
=
onTouchBasedDevice
()
||
''
,
isTouch
=
onTouchBasedDevice
()
||
''
,
storage
=
CookieStorage
(
'video_player'
),
storage
=
VideoStorage
(
'VideoState'
,
id
);
speed
=
storage
.
getItem
(
'video_speed_'
+
id
)
||
el
.
data
(
'speed'
)
||
storage
.
getItem
(
'general_speed'
)
||
el
.
data
(
'general-speed'
)
||
'1.0'
;
if
(
isTouch
)
{
if
(
isTouch
)
{
el
.
addClass
(
'is-touch'
);
el
.
addClass
(
'is-touch'
);
...
@@ -399,7 +410,6 @@ function (VideoPlayer, CookieStorage) {
...
@@ -399,7 +410,6 @@ function (VideoPlayer, CookieStorage) {
id
:
id
,
id
:
id
,
isFullScreen
:
false
,
isFullScreen
:
false
,
isTouch
:
isTouch
,
isTouch
:
isTouch
,
speed
:
Number
(
speed
).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
),
storage
:
storage
storage
:
storage
});
});
...
@@ -411,7 +421,7 @@ function (VideoPlayer, CookieStorage) {
...
@@ -411,7 +421,7 @@ function (VideoPlayer, CookieStorage) {
// are "read only", so don't modify them. All variable content lives in
// are "read only", so don't modify them. All variable content lives in
// 'state' object.
// 'state' object.
// jQuery .data() return object with keys in lower camelCase format.
// jQuery .data() return object with keys in lower camelCase format.
this
.
config
=
$
.
extend
({},
_getConfiguration
(
el
.
data
()),
{
this
.
config
=
$
.
extend
({},
_getConfiguration
(
el
.
data
()
,
storage
),
{
element
:
element
,
element
:
element
,
fadeOutTimeout
:
1400
,
fadeOutTimeout
:
1400
,
captionsFreezeTime
:
10000
,
captionsFreezeTime
:
10000
,
...
@@ -422,6 +432,10 @@ function (VideoPlayer, CookieStorage) {
...
@@ -422,6 +432,10 @@ function (VideoPlayer, CookieStorage) {
this
.
config
.
endTime
=
null
;
this
.
config
.
endTime
=
null
;
}
}
this
.
speed
=
Number
(
this
.
config
.
speed
||
this
.
config
.
generalSpeed
).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
);
if
(
!
(
_parseYouTubeIDs
(
this
)))
{
if
(
!
(
_parseYouTubeIDs
(
this
)))
{
// 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.
...
@@ -637,8 +651,8 @@ function (VideoPlayer, CookieStorage) {
...
@@ -637,8 +651,8 @@ function (VideoPlayer, CookieStorage) {
}
}
if
(
updateStorage
)
{
if
(
updateStorage
)
{
this
.
storage
.
setItem
(
'
video_speed_'
+
this
.
id
,
this
.
speed
,
useSession
);
this
.
storage
.
setItem
(
'
speed'
,
this
.
speed
,
true
);
this
.
storage
.
setItem
(
'general_speed'
,
this
.
speed
,
useSession
);
this
.
storage
.
setItem
(
'general_speed'
,
this
.
speed
);
}
}
}
}
...
@@ -659,20 +673,34 @@ function (VideoPlayer, CookieStorage) {
...
@@ -659,20 +673,34 @@ function (VideoPlayer, CookieStorage) {
return
xhr
;
return
xhr
;
}
}
function
stopBuffering
()
{
function
saveState
(
async
,
data
)
{
var
video
;
if
(
!
(
$
.
isPlainObject
(
data
)))
{
data
=
{
saved_video_position
:
this
.
videoPlayer
.
currentTime
};
}
if
(
this
.
videoType
===
'html5'
)
{
if
(
data
.
speed
)
{
// HTML5 player haven't default way to abort bufferization.
this
.
storage
.
setItem
(
'speed'
,
data
.
speed
,
true
);
// In this case we simply resetting source and call load().
video
=
this
.
videoPlayer
.
player
.
video
;
video
.
src
=
''
;
video
.
load
();
}
}
if
(
data
.
saved_video_position
)
{
this
.
storage
.
setItem
(
'savedVideoPosition'
,
data
.
saved_video_position
,
true
);
data
.
saved_video_position
=
Time
.
formatFull
(
data
.
saved_video_position
);
}
$
.
ajax
({
url
:
this
.
config
.
saveStateUrl
,
type
:
'POST'
,
async
:
async
?
true
:
false
,
dataType
:
'json'
,
data
:
data
,
});
}
}
function
youtubeId
(
speed
)
{
function
youtubeId
(
speed
)
{
return
this
.
videos
[
speed
||
this
.
speed
];
return
this
.
videos
[
speed
||
this
.
speed
]
||
this
.
videos
[
'1.0'
]
;
}
}
function
getDuration
()
{
function
getDuration
()
{
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
d8144b0d
...
@@ -67,6 +67,8 @@ function (HTML5Video, Resizer) {
...
@@ -67,6 +67,8 @@ function (HTML5Video, Resizer) {
// metadata is loaded, which normally happens just after the video
// metadata is loaded, which normally happens just after the video
// starts playing. Just after that configurations can be applied.
// starts playing. Just after that configurations can be applied.
state
.
videoPlayer
.
ready
=
_
.
once
(
function
()
{
state
.
videoPlayer
.
ready
=
_
.
once
(
function
()
{
$
(
window
).
on
(
'unload'
,
state
.
saveState
);
if
(
state
.
currentPlayerMode
!==
'flash'
)
{
if
(
state
.
currentPlayerMode
!==
'flash'
)
{
state
.
videoPlayer
.
onSpeedChange
(
state
.
speed
);
state
.
videoPlayer
.
onSpeedChange
(
state
.
speed
);
}
}
...
@@ -292,8 +294,7 @@ function (HTML5Video, Resizer) {
...
@@ -292,8 +294,7 @@ function (HTML5Video, Resizer) {
// (currentTime) and its duration.
// (currentTime) and its duration.
// It is called at a regular interval when the video is playing.
// It is called at a regular interval when the video is playing.
function
update
()
{
function
update
()
{
this
.
videoPlayer
.
currentTime
=
this
.
videoPlayer
.
player
this
.
videoPlayer
.
currentTime
=
this
.
videoPlayer
.
player
.
getCurrentTime
();
.
getCurrentTime
();
if
(
isFinite
(
this
.
videoPlayer
.
currentTime
))
{
if
(
isFinite
(
this
.
videoPlayer
.
currentTime
))
{
this
.
videoPlayer
.
updatePlayTime
(
this
.
videoPlayer
.
currentTime
);
this
.
videoPlayer
.
updatePlayTime
(
this
.
videoPlayer
.
currentTime
);
...
@@ -379,14 +380,7 @@ function (HTML5Video, Resizer) {
...
@@ -379,14 +380,7 @@ function (HTML5Video, Resizer) {
this
.
el
.
trigger
(
'speedchange'
,
arguments
);
this
.
el
.
trigger
(
'speedchange'
,
arguments
);
$
.
ajax
({
this
.
saveState
(
true
,
{
speed
:
newSpeed
});
url
:
this
.
config
.
saveStateUrl
,
type
:
'POST'
,
dataType
:
'json'
,
data
:
{
speed
:
newSpeed
},
});
}
}
// Every 200 ms, if the video is playing, we call the function update, via
// Every 200 ms, if the video is playing, we call the function update, via
...
@@ -485,6 +479,7 @@ function (HTML5Video, Resizer) {
...
@@ -485,6 +479,7 @@ function (HTML5Video, Resizer) {
this
.
trigger
(
'videoCaption.pause'
,
null
);
this
.
trigger
(
'videoCaption.pause'
,
null
);
}
}
this
.
saveState
(
true
);
this
.
el
.
trigger
(
'pause'
,
arguments
);
this
.
el
.
trigger
(
'pause'
,
arguments
);
}
}
...
@@ -640,7 +635,7 @@ function (HTML5Video, Resizer) {
...
@@ -640,7 +635,7 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
onEnded
();
this
.
videoPlayer
.
onEnded
();
break
;
break
;
case
this
.
videoPlayer
.
PlayerState
.
CUED
:
case
this
.
videoPlayer
.
PlayerState
.
CUED
:
this
.
videoPlayer
.
player
.
seekTo
(
this
.
videoPlayer
.
s
tartTime
,
true
);
this
.
videoPlayer
.
player
.
seekTo
(
this
.
videoPlayer
.
s
eekToTimeOnCued
,
true
);
// We need to call play() explicitly because after the call
// We need to call play() explicitly because after the call
// to functions cueVideoById() followed by seekTo() the video
// to functions cueVideoById() followed by seekTo() the video
// is in a PAUSED state.
// is in a PAUSED state.
...
@@ -652,28 +647,26 @@ function (HTML5Video, Resizer) {
...
@@ -652,28 +647,26 @@ function (HTML5Video, Resizer) {
}
}
function
updatePlayTime
(
time
)
{
function
updatePlayTime
(
time
)
{
var
duration
=
this
.
videoPlayer
.
duration
(),
var
videoPlayer
=
this
.
videoPlayer
,
durationChange
,
tempStartTime
,
tempEndTime
,
youTubeId
;
duration
=
this
.
videoPlayer
.
duration
(),
savedVideoPosition
=
this
.
config
.
savedVideoPosition
,
isNewSpeed
=
videoPlayer
.
seekToStartTimeOldSpeed
!==
this
.
speed
,
durationChange
,
tempStartTime
,
tempEndTime
,
youTubeId
,
startTime
,
endTime
;
if
(
if
(
duration
>
0
&&
duration
>
0
&&
(
(
isNewSpeed
||
videoPlayer
.
initialSeekToStartTime
)
this
.
videoPlayer
.
seekToStartTimeOldSpeed
!==
this
.
speed
||
this
.
videoPlayer
.
initialSeekToStartTime
)
)
{
)
{
if
(
if
(
isNewSpeed
&&
videoPlayer
.
initialSeekToStartTime
===
false
)
{
this
.
videoPlayer
.
seekToStartTimeOldSpeed
!==
this
.
speed
&&
this
.
videoPlayer
.
initialSeekToStartTime
===
false
)
{
durationChange
=
true
;
durationChange
=
true
;
}
else
{
// this.videoPlayer.initialSeekToStartTime === true
}
else
{
// this.videoPlayer.initialSeekToStartTime === true
this
.
videoPlayer
.
initialSeekToStartTime
=
false
;
videoPlayer
.
initialSeekToStartTime
=
false
;
durationChange
=
false
;
durationChange
=
false
;
}
}
this
.
videoPlayer
.
seekToStartTimeOldSpeed
=
this
.
speed
;
videoPlayer
.
seekToStartTimeOldSpeed
=
this
.
speed
;
// Current startTime and endTime could have already been reset.
// Current startTime and endTime could have already been reset.
// We will remember their current values, and reset them at the
// We will remember their current values, and reset them at the
...
@@ -681,22 +674,20 @@ function (HTML5Video, Resizer) {
...
@@ -681,22 +674,20 @@ function (HTML5Video, Resizer) {
// times so that the range on the slider gets correctly updated in
// times so that the range on the slider gets correctly updated in
// the case of speed change in Flash player mode (for YouTube
// the case of speed change in Flash player mode (for YouTube
// videos).
// videos).
tempStartTime
=
this
.
videoPlayer
.
startTime
;
tempStartTime
=
videoPlayer
.
startTime
;
tempEndTime
=
this
.
videoPlayer
.
endTime
;
tempEndTime
=
videoPlayer
.
endTime
;
// We retrieve the original times. They could have been changed due
// We retrieve the original times. They could have been changed due
// to the fact of speed change (duration change). This happens when
// to the fact of speed change (duration change). This happens when
// in YouTube Flash mode. There each speed is a different video,
// in YouTube Flash mode. There each speed is a different video,
// with a different length.
// with a different length.
this
.
videoPlayer
.
startTime
=
this
.
config
.
startTime
;
videoPlayer
.
startTime
=
this
.
config
.
startTime
;
this
.
videoPlayer
.
endTime
=
this
.
config
.
endTime
;
videoPlayer
.
endTime
=
this
.
config
.
endTime
;
if
(
this
.
videoPlayer
.
startTime
>
duration
)
{
if
(
videoPlayer
.
startTime
>
duration
)
{
this
.
videoPlayer
.
startTime
=
0
;
videoPlayer
.
startTime
=
0
;
}
else
{
}
else
if
(
this
.
currentPlayerMode
===
'flash'
)
{
if
(
this
.
currentPlayerMode
===
'flash'
)
{
videoPlayer
.
startTime
/=
Number
(
this
.
speed
);
this
.
videoPlayer
.
startTime
/=
Number
(
this
.
speed
);
}
}
}
// An `endTime` of `null` means that either the user didn't set
// An `endTime` of `null` means that either the user didn't set
...
@@ -708,13 +699,10 @@ function (HTML5Video, Resizer) {
...
@@ -708,13 +699,10 @@ function (HTML5Video, Resizer) {
// sometimes in YouTube mode the duration changes slightly during
// sometimes in YouTube mode the duration changes slightly during
// the course of playback. This would cause the video to pause just
// the course of playback. This would cause the video to pause just
// before the actual end of the video.
// before the actual end of the video.
if
(
if
(
videoPlayer
.
endTime
!==
null
)
{
this
.
videoPlayer
.
endTime
!==
null
&&
if
(
videoPlayer
.
endTime
>
duration
)
{
this
.
videoPlayer
.
endTime
>
duration
this
.
videoPlayer
.
endTime
=
null
;
)
{
}
else
if
(
this
.
currentPlayerMode
===
'flash'
)
{
this
.
videoPlayer
.
endTime
=
null
;
}
else
if
(
this
.
videoPlayer
.
endTime
!==
null
)
{
if
(
this
.
currentPlayerMode
===
'flash'
)
{
this
.
videoPlayer
.
endTime
/=
Number
(
this
.
speed
);
this
.
videoPlayer
.
endTime
/=
Number
(
this
.
speed
);
}
}
}
}
...
@@ -734,9 +722,22 @@ function (HTML5Video, Resizer) {
...
@@ -734,9 +722,22 @@ function (HTML5Video, Resizer) {
// performed already such a seek.
// performed already such a seek.
if
(
if
(
durationChange
===
false
&&
durationChange
===
false
&&
this
.
videoPlayer
.
startTime
>
0
&&
(
videoPlayer
.
startTime
>
0
||
savedVideoPosition
!==
0
)
&&
!
(
tempStartTime
===
0
&&
tempEndTime
===
null
)
!
(
tempStartTime
===
0
&&
tempEndTime
===
null
)
)
{
)
{
startTime
=
this
.
videoPlayer
.
startTime
;
endTime
=
this
.
videoPlayer
.
endTime
;
if
(
startTime
)
{
if
(
startTime
<
savedVideoPosition
&&
endTime
>
savedVideoPosition
)
{
time
=
savedVideoPosition
;
}
else
{
time
=
startTime
;
}
}
else
{
time
=
savedVideoPosition
;
}
// After a bug came up (BLD-708: "In Firefox YouTube video with
// After a bug came up (BLD-708: "In Firefox YouTube video with
// start time plays from 00:00:00") the video refused to play
// start time plays from 00:00:00") the video refused to play
// from start time, and only played from the beginning.
// from start time, and only played from the beginning.
...
@@ -765,9 +766,10 @@ function (HTML5Video, Resizer) {
...
@@ -765,9 +766,10 @@ function (HTML5Video, Resizer) {
// times
// times
this
.
videoPlayer
.
skipOnEndedStartEndReset
=
true
;
this
.
videoPlayer
.
skipOnEndedStartEndReset
=
true
;
this
.
videoPlayer
.
player
.
cueVideoById
(
youTubeId
,
this
.
videoPlayer
.
startTime
);
this
.
videoPlayer
.
seekToTimeOnCued
=
time
;
this
.
videoPlayer
.
player
.
cueVideoById
(
youTubeId
,
time
);
}
else
{
}
else
{
this
.
videoPlayer
.
player
.
seekTo
(
t
his
.
videoPlayer
.
startT
ime
);
this
.
videoPlayer
.
player
.
seekTo
(
time
);
}
}
}
}
...
@@ -775,8 +777,8 @@ function (HTML5Video, Resizer) {
...
@@ -775,8 +777,8 @@ function (HTML5Video, Resizer) {
// already reset (a seek event happened, the video already ended
// already reset (a seek event happened, the video already ended
// once, or endTime has already been reached once).
// once, or endTime has already been reached once).
if
(
tempStartTime
===
0
&&
tempEndTime
===
null
)
{
if
(
tempStartTime
===
0
&&
tempEndTime
===
null
)
{
this
.
videoPlayer
.
startTime
=
0
;
videoPlayer
.
startTime
=
0
;
this
.
videoPlayer
.
endTime
=
null
;
videoPlayer
.
endTime
=
null
;
}
}
}
}
...
...
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
View file @
d8144b0d
...
@@ -104,7 +104,8 @@ function () {
...
@@ -104,7 +104,8 @@ function () {
// whole slider). Remember that endTime === null means the end time
// whole slider). Remember that endTime === null means the end time
// is set to the end of video by default.
// is set to the end of video by default.
function
updateStartEndTimeRegion
(
params
)
{
function
updateStartEndTimeRegion
(
params
)
{
var
left
,
width
,
start
,
end
,
duration
,
rangeParams
;
var
isFlashMode
=
this
.
currentPlayerMode
===
'flash'
,
left
,
width
,
start
,
end
,
duration
,
rangeParams
;
// We must have a duration in order to determine the area of range.
// We must have a duration in order to determine the area of range.
// It also must be non-zero.
// It also must be non-zero.
...
@@ -119,22 +120,16 @@ function () {
...
@@ -119,22 +120,16 @@ function () {
if
(
start
>
duration
)
{
if
(
start
>
duration
)
{
start
=
0
;
start
=
0
;
}
else
{
}
else
if
(
isFlashMode
)
{
if
(
this
.
currentPlayerMode
===
'flash'
)
{
start
/=
Number
(
this
.
speed
);
start
/=
Number
(
this
.
speed
);
}
}
}
// If end is set to null, or it is greater than the duration of the
// If end is set to null, or it is greater than the duration of the
// video, then we set it to the end of the video.
// video, then we set it to the end of the video.
if
(
if
(
end
===
null
||
end
>
duration
)
{
end
===
null
||
end
>
duration
)
{
end
=
duration
;
end
=
duration
;
}
else
if
(
end
!==
null
)
{
}
else
if
(
isFlashMode
)
{
if
(
this
.
currentPlayerMode
===
'flash'
)
{
end
/=
Number
(
this
.
speed
);
end
/=
Number
(
this
.
speed
);
}
}
}
// Don't build a range if it takes up the whole slider.
// Don't build a range if it takes up the whole slider.
...
...
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
d8144b0d
...
@@ -75,22 +75,13 @@ function (
...
@@ -75,22 +75,13 @@ function (
window
.
Video
=
function
(
element
)
{
window
.
Video
=
function
(
element
)
{
var
state
;
var
state
;
// Stop bufferization of previous video on sequence change.
// Problem: multiple video tags with the same src cannot
// play together. The second tag waiting when first video will be fully loaded.
// That's why we abort bufferization forcibly.
$
(
element
).
closest
(
'.sequence'
).
bind
(
'sequence:change'
,
function
(
e
){
if
(
previousState
!==
null
&&
typeof
previousState
.
videoPlayer
!==
'undefined'
)
{
previousState
.
stopBuffering
();
$
(
e
.
currentTarget
).
unbind
(
'sequence:change'
);
}
});
// Check for existance of previous state, uninitialize it if necessary, and create a new state.
// Check for existance of previous state, uninitialize it if necessary, and create a new state.
// Store new state for future invocation of this module consturctor function.
// Store new state for future invocation of this module consturctor function.
if
(
previousState
!==
null
&&
typeof
previousState
.
videoPlayer
!==
'undefined'
)
{
if
(
previousState
&&
previousState
.
videoPlayer
)
{
previousState
.
videoPlayer
.
onPause
();
previousState
.
saveState
(
true
);
$
(
window
).
off
(
'unload'
,
previousState
.
saveState
);
}
}
state
=
{};
state
=
{};
previousState
=
state
;
previousState
=
state
;
...
@@ -110,6 +101,8 @@ function (
...
@@ -110,6 +101,8 @@ function (
youtubeXhr
=
state
.
youtubeXhr
;
youtubeXhr
=
state
.
youtubeXhr
;
}
}
$
(
element
).
find
(
'.video'
).
data
(
'video-player-state'
,
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/video_module.py
View file @
d8144b0d
...
@@ -30,7 +30,7 @@ from xmodule.contentstore.django import contentstore
...
@@ -30,7 +30,7 @@ from xmodule.contentstore.django import contentstore
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
Float
,
Boolean
,
List
,
Integer
,
ScopeIds
from
xblock.fields
import
Scope
,
String
,
Float
,
Boolean
,
List
,
ScopeIds
from
xmodule.fields
import
RelativeTime
from
xmodule.fields
import
RelativeTime
from
xmodule.modulestore.inheritance
import
InheritanceKeyValueStore
from
xmodule.modulestore.inheritance
import
InheritanceKeyValueStore
...
@@ -46,10 +46,10 @@ class VideoFields(object):
...
@@ -46,10 +46,10 @@ class VideoFields(object):
default
=
"Video"
,
default
=
"Video"
,
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
position
=
Integer
(
saved_video_position
=
RelativeTime
(
help
=
"Current position in the video"
,
help
=
"Current position in the video"
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
default
=
0
default
=
datetime
.
timedelta
(
seconds
=
0
)
)
)
show_captions
=
Boolean
(
show_captions
=
Boolean
(
help
=
"This controls whether or not captions are shown by default."
,
help
=
"This controls whether or not captions are shown by default."
,
...
@@ -167,7 +167,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -167,7 +167,7 @@ class VideoModule(VideoFields, XModule):
# index. We do that to avoid issues that occurs in tests.
# index. We do that to avoid issues that occurs in tests.
js
=
{
js
=
{
'js'
:
[
'js'
:
[
resource_string
(
__name__
,
'js/src/video/00_
cookie
_storage.js'
),
resource_string
(
__name__
,
'js/src/video/00_
video
_storage.js'
),
resource_string
(
__name__
,
'js/src/video/00_resizer.js'
),
resource_string
(
__name__
,
'js/src/video/00_resizer.js'
),
resource_string
(
__name__
,
'js/src/video/01_initialize.js'
),
resource_string
(
__name__
,
'js/src/video/01_initialize.js'
),
resource_string
(
__name__
,
'js/src/video/025_focus_grabber.js'
),
resource_string
(
__name__
,
'js/src/video/025_focus_grabber.js'
),
...
@@ -186,15 +186,21 @@ class VideoModule(VideoFields, XModule):
...
@@ -186,15 +186,21 @@ class VideoModule(VideoFields, XModule):
js_module_name
=
"Video"
js_module_name
=
"Video"
def
handle_ajax
(
self
,
dispatch
,
data
):
def
handle_ajax
(
self
,
dispatch
,
data
):
ACCEPTED_KEYS
=
[
'speed
'
]
accepted_keys
=
[
'speed'
,
'saved_video_position
'
]
if
dispatch
==
'save_user_state'
:
if
dispatch
==
'save_user_state'
:
for
key
in
data
:
for
key
in
data
:
if
hasattr
(
self
,
key
)
and
key
in
ACCEPTED_KEYS
:
if
hasattr
(
self
,
key
)
and
key
in
accepted_keys
:
setattr
(
self
,
key
,
json
.
loads
(
data
[
key
]))
if
key
==
'saved_video_position'
:
relative_position
=
RelativeTime
.
isotime_to_timedelta
(
data
[
key
])
self
.
saved_video_position
=
relative_position
else
:
setattr
(
self
,
key
,
json
.
loads
(
data
[
key
]))
if
key
==
'speed'
:
if
key
==
'speed'
:
self
.
global_speed
=
self
.
speed
self
.
global_speed
=
self
.
speed
log
.
debug
(
u"Test."
)
return
json
.
dumps
({
'success'
:
True
})
return
json
.
dumps
({
'success'
:
True
})
log
.
debug
(
u"GET {0}"
.
format
(
data
))
log
.
debug
(
u"GET {0}"
.
format
(
data
))
...
@@ -235,6 +241,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -235,6 +241,7 @@ class VideoModule(VideoFields, XModule):
'sources'
:
sources
,
'sources'
:
sources
,
'speed'
:
json
.
dumps
(
self
.
speed
),
'speed'
:
json
.
dumps
(
self
.
speed
),
'general_speed'
:
self
.
global_speed
,
'general_speed'
:
self
.
global_speed
,
'saved_video_position'
:
self
.
saved_video_position
.
total_seconds
(),
'start'
:
self
.
start_time
.
total_seconds
(),
'start'
:
self
.
start_time
.
total_seconds
(),
'sub'
:
self
.
sub
,
'sub'
:
self
.
sub
,
'track'
:
track_url
,
'track'
:
track_url
,
...
@@ -293,6 +300,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -293,6 +300,7 @@ class VideoModule(VideoFields, XModule):
return
response
return
response
class
VideoDescriptor
(
VideoFields
,
TabsEditingDescriptor
,
EmptyDataRawDescriptor
):
class
VideoDescriptor
(
VideoFields
,
TabsEditingDescriptor
,
EmptyDataRawDescriptor
):
"""Descriptor for `VideoModule`."""
"""Descriptor for `VideoModule`."""
module_class
=
VideoModule
module_class
=
VideoModule
...
...
lms/djangoapps/courseware/features/video.feature
View file @
d8144b0d
...
@@ -2,6 +2,17 @@
...
@@ -2,6 +2,17 @@
Feature
:
LMS.Video component
Feature
:
LMS.Video component
As a student, I want to view course videos in LMS.
As a student, I want to view course videos in LMS.
# 0
Scenario
:
Video component stores position correctly when page is reloaded
Given
the course has a Video component in Youtube mode
Then
when I view the video it has rendered in Youtube mode
And
I click video button
"play"
Then
I seek video to
"10"
seconds
And
I click video button
"pause"
And
I reload the page
And
I click video button
"play"
Then I see video starts playing from "0
:
10"
position
# 1
# 1
Scenario
:
Video component is fully rendered in the LMS in HTML5 mode
Scenario
:
Video component is fully rendered in the LMS in HTML5 mode
Given
the course has a Video component in HTML5 mode
Given
the course has a Video component in HTML5 mode
...
...
lms/djangoapps/courseware/features/video.py
View file @
d8144b0d
...
@@ -15,6 +15,16 @@ HTML5_SOURCES_INCORRECT = [
...
@@ -15,6 +15,16 @@ HTML5_SOURCES_INCORRECT = [
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp99'
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp99'
]
]
VIDEO_BUTTONS
=
{
'CC'
:
'.hide-subtitles'
,
'volume'
:
'.volume'
,
'play'
:
'.video_control.play'
,
'pause'
:
'.video_control.pause'
,
}
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY
=
0.5
coursenum
=
'test_course'
coursenum
=
'test_course'
sequence
=
{}
sequence
=
{}
...
@@ -151,3 +161,26 @@ def _change_video_speed(speed):
...
@@ -151,3 +161,26 @@ def _change_video_speed(speed):
world
.
browser
.
execute_script
(
"$('.speeds').addClass('open')"
)
world
.
browser
.
execute_script
(
"$('.speeds').addClass('open')"
)
speed_css
=
'li[data-speed="{0}"] a'
.
format
(
speed
)
speed_css
=
'li[data-speed="{0}"] a'
.
format
(
speed
)
world
.
css_click
(
speed_css
)
world
.
css_click
(
speed_css
)
@step
(
'I click video button "([^"]*)"$'
)
def
click_button_video
(
_step
,
button_type
):
world
.
wait
(
DELAY
)
world
.
wait_for_ajax_complete
()
button
=
button_type
.
strip
()
world
.
css_click
(
VIDEO_BUTTONS
[
button
])
@step
(
'I see video starts playing from "([^"]*)" position$'
)
def
start_playing_video_from_n_seconds
(
_step
,
position
):
world
.
wait_for
(
func
=
lambda
_
:
world
.
css_html
(
'.vidtime'
)[:
4
]
==
position
.
strip
(),
timeout
=
5
)
@step
(
'I seek video to "([^"]*)" seconds$'
)
def
seek_video_to_n_seconds
(
_step
,
seconds
):
time
=
float
(
seconds
.
strip
())
jsCode
=
"$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0:f}}})"
.
format
(
time
)
world
.
browser
.
execute_script
(
jsCode
)
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
d8144b0d
...
@@ -69,6 +69,7 @@ class TestVideoYouTube(TestVideo):
...
@@ -69,6 +69,7 @@ class TestVideoYouTube(TestVideo):
'speed'
:
'null'
,
'speed'
:
'null'
,
'general_speed'
:
1.0
,
'general_speed'
:
1.0
,
'start'
:
3603.0
,
'start'
:
3603.0
,
'saved_video_position'
:
0.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
None
,
'track'
:
None
,
'youtube_streams'
:
_create_youtube_string
(
self
.
item_module
),
'youtube_streams'
:
_create_youtube_string
(
self
.
item_module
),
...
@@ -124,6 +125,7 @@ class TestVideoNonYouTube(TestVideo):
...
@@ -124,6 +125,7 @@ class TestVideoNonYouTube(TestVideo):
'speed'
:
'null'
,
'speed'
:
'null'
,
'general_speed'
:
1.0
,
'general_speed'
:
1.0
,
'start'
:
3603.0
,
'start'
:
3603.0
,
'saved_video_position'
:
0.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
None
,
'track'
:
None
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
...
@@ -202,6 +204,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -202,6 +204,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
u'webm'
:
u'example.webm'
u'webm'
:
u'example.webm'
},
},
'start'
:
3603.0
,
'start'
:
3603.0
,
'saved_video_position'
:
0.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'speed'
:
'null'
,
'speed'
:
'null'
,
'general_speed'
:
1.0
,
'general_speed'
:
1.0
,
...
@@ -308,6 +311,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -308,6 +311,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
'speed'
:
'null'
,
'speed'
:
'null'
,
'general_speed'
:
1.0
,
'general_speed'
:
1.0
,
'start'
:
3603.0
,
'start'
:
3603.0
,
'saved_video_position'
:
0.0
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
None
,
'track'
:
None
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
...
...
lms/templates/video.html
View file @
d8144b0d
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
data-show-captions=
"${show_captions}"
data-show-captions=
"${show_captions}"
data-general-speed=
"${general_speed}"
data-general-speed=
"${general_speed}"
data-speed=
"${speed}"
data-speed=
"${speed}"
data-saved-video-position=
"${saved_video_position}"
data-start=
"${start}"
data-start=
"${start}"
data-end=
"${end}"
data-end=
"${end}"
data-caption-asset-path=
"${caption_asset_path}"
data-caption-asset-path=
"${caption_asset_path}"
...
...
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