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
450dfe17
Commit
450dfe17
authored
Apr 09, 2014
by
Valera Rozuvan
Committed by
Valera Rozuvan
Apr 23, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TESTS: Add tests for fix of end-time in the video player.
BLD-662
parent
dc373f22
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
290 additions
and
92 deletions
+290
-92
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
+1
-1
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+27
-11
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+5
-0
common/lib/xmodule/xmodule/js/src/video/02_html5_video.js
+2
-1
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+105
-35
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
+13
-2
lms/djangoapps/courseware/features/common.py
+1
-1
lms/djangoapps/courseware/features/video.feature
+112
-30
lms/djangoapps/courseware/features/video.py
+24
-11
No files found.
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
View file @
450dfe17
...
@@ -117,7 +117,7 @@
...
@@ -117,7 +117,7 @@
runs
(
function
()
{
runs
(
function
()
{
expect
(
state
.
videoPlayer
.
player
.
getPlayerState
())
expect
(
state
.
videoPlayer
.
player
.
getPlayerState
())
.
toBe
(
STATUS
.
PLAY
ING
);
.
toBe
(
STATUS
.
BUFFER
ING
);
});
});
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
450dfe17
...
@@ -366,19 +366,22 @@ function (VideoPlayer) {
...
@@ -366,19 +366,22 @@ function (VideoPlayer) {
});
});
it
(
'Slider event causes log update'
,
function
()
{
it
(
'Slider event causes log update'
,
function
()
{
runs
(
function
()
{
runs
(
function
()
{
var
currentTime
=
state
.
videoPlayer
.
currentTime
;
spyOn
(
state
.
videoPlayer
,
'log'
);
spyOn
(
state
.
videoPlayer
,
'log'
);
state
.
videoProgressSlider
.
onSlide
(
state
.
videoProgressSlider
.
onSlide
(
jQuery
.
Event
(
'slide'
),
{
value
:
2
}
jQuery
.
Event
(
'slide'
),
{
value
:
2
}
);
);
});
waitsFor
(
function
()
{
return
state
.
videoPlayer
.
currentTime
>=
2
;
},
'currentTime is less than 2 seconds'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
state
.
videoPlayer
.
log
).
toHaveBeenCalledWith
(
expect
(
state
.
videoPlayer
.
log
).
toHaveBeenCalledWith
(
'seek_video'
,
'seek_video'
,
{
{
old_time
:
currentTime
,
old_time
:
jasmine
.
any
(
Number
)
,
new_time
:
2
,
new_time
:
2
,
type
:
'onSlideSeek'
type
:
'onSlideSeek'
}
}
...
@@ -388,25 +391,37 @@ function (VideoPlayer) {
...
@@ -388,25 +391,37 @@ function (VideoPlayer) {
it
(
'seek the player'
,
function
()
{
it
(
'seek the player'
,
function
()
{
runs
(
function
()
{
runs
(
function
()
{
spyOn
(
state
.
videoPlayer
.
player
,
'seekTo'
);
spyOn
(
state
.
videoPlayer
.
player
,
'seekTo'
)
.
andCallThrough
()
;
state
.
videoProgressSlider
.
onSlide
(
state
.
videoProgressSlider
.
onSlide
(
jQuery
.
Event
(
'slide'
),
{
value
:
6
0
}
jQuery
.
Event
(
'slide'
),
{
value
:
3
0
}
);
);
});
waitsFor
(
function
()
{
return
state
.
videoPlayer
.
currentTime
>=
30
;
},
'currentTime is less than 30 seconds'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
state
.
videoPlayer
.
player
.
seekTo
)
expect
(
state
.
videoPlayer
.
player
.
seekTo
)
.
toHaveBeenCalledWith
(
6
0
,
true
);
.
toHaveBeenCalledWith
(
3
0
,
true
);
});
});
});
});
it
(
'call updatePlayTime on player'
,
function
()
{
it
(
'call updatePlayTime on player'
,
function
()
{
runs
(
function
()
{
runs
(
function
()
{
spyOn
(
state
.
videoPlayer
,
'updatePlayTime'
);
spyOn
(
state
.
videoPlayer
,
'updatePlayTime'
)
.
andCallThrough
()
;
state
.
videoProgressSlider
.
onSlide
(
state
.
videoProgressSlider
.
onSlide
(
jQuery
.
Event
(
'slide'
),
{
value
:
6
0
}
jQuery
.
Event
(
'slide'
),
{
value
:
3
0
}
);
);
});
waitsFor
(
function
()
{
return
state
.
videoPlayer
.
currentTime
>=
30
;
},
'currentTime is less than 30 seconds'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
state
.
videoPlayer
.
updatePlayTime
)
expect
(
state
.
videoPlayer
.
updatePlayTime
)
.
toHaveBeenCalledWith
(
60
);
.
toHaveBeenCalledWith
(
jasmine
.
any
(
Number
)
);
});
});
});
});
...
@@ -1070,6 +1085,7 @@ function (VideoPlayer) {
...
@@ -1070,6 +1085,7 @@ function (VideoPlayer) {
youtubeId
:
jasmine
.
createSpy
().
andReturn
(
'videoId'
),
youtubeId
:
jasmine
.
createSpy
().
andReturn
(
'videoId'
),
isFlashMode
:
jasmine
.
createSpy
().
andReturn
(
false
),
isFlashMode
:
jasmine
.
createSpy
().
andReturn
(
false
),
isHtml5Mode
:
jasmine
.
createSpy
().
andReturn
(
true
),
isHtml5Mode
:
jasmine
.
createSpy
().
andReturn
(
true
),
isYoutubeType
:
jasmine
.
createSpy
().
andReturn
(
true
),
setPlayerMode
:
jasmine
.
createSpy
(),
setPlayerMode
:
jasmine
.
createSpy
(),
videoPlayer
:
{
videoPlayer
:
{
currentTime
:
60
,
currentTime
:
60
,
...
@@ -1104,12 +1120,12 @@ function (VideoPlayer) {
...
@@ -1104,12 +1120,12 @@ function (VideoPlayer) {
});
});
it
(
'in HTML5 mode'
,
function
()
{
it
(
'in HTML5 mode'
,
function
()
{
state
.
isYoutubeType
.
andReturn
(
false
);
VideoPlayer
.
prototype
.
setPlaybackRate
.
call
(
state
,
'0.75'
);
VideoPlayer
.
prototype
.
setPlaybackRate
.
call
(
state
,
'0.75'
);
expect
(
state
.
videoPlayer
.
player
.
setPlaybackRate
).
toHaveBeenCalledWith
(
'0.75'
);
expect
(
state
.
videoPlayer
.
player
.
setPlaybackRate
).
toHaveBeenCalledWith
(
'0.75'
);
});
});
it
(
'Youtube video in FF, with new speed equal 1.0'
,
function
()
{
it
(
'Youtube video in FF, with new speed equal 1.0'
,
function
()
{
state
.
videoType
=
'youtube'
;
state
.
browserIsFirefox
=
true
;
state
.
browserIsFirefox
=
true
;
state
.
videoPlayer
.
isPlaying
.
andReturn
(
false
);
state
.
videoPlayer
.
isPlaying
.
andReturn
(
false
);
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
450dfe17
...
@@ -68,6 +68,7 @@ function (VideoPlayer, VideoStorage) {
...
@@ -68,6 +68,7 @@ function (VideoPlayer, VideoStorage) {
initialize
:
initialize
,
initialize
:
initialize
,
isHtml5Mode
:
isHtml5Mode
,
isHtml5Mode
:
isHtml5Mode
,
isFlashMode
:
isFlashMode
,
isFlashMode
:
isFlashMode
,
isYoutubeType
:
isYoutubeType
,
parseSpeed
:
parseSpeed
,
parseSpeed
:
parseSpeed
,
parseVideoSources
:
parseVideoSources
,
parseVideoSources
:
parseVideoSources
,
parseYoutubeStreams
:
parseYoutubeStreams
,
parseYoutubeStreams
:
parseYoutubeStreams
,
...
@@ -847,6 +848,10 @@ function (VideoPlayer, VideoStorage) {
...
@@ -847,6 +848,10 @@ function (VideoPlayer, VideoStorage) {
return
this
.
getPlayerMode
()
===
'html5'
;
return
this
.
getPlayerMode
()
===
'html5'
;
}
}
function
isYoutubeType
()
{
return
this
.
videoType
===
'youtube'
;
}
function
speedToString
(
speed
)
{
function
speedToString
(
speed
)
{
return
parseFloat
(
speed
).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
);
return
parseFloat
(
speed
).
toFixed
(
2
).
replace
(
/
\.
00$/
,
'.0'
);
}
}
...
...
common/lib/xmodule/xmodule/js/src/video/02_html5_video.js
View file @
450dfe17
...
@@ -275,12 +275,13 @@ function () {
...
@@ -275,12 +275,13 @@ function () {
// Register the 'play' event.
// Register the 'play' event.
this
.
video
.
addEventListener
(
'play'
,
function
()
{
this
.
video
.
addEventListener
(
'play'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAY
ING
;
_this
.
playerState
=
HTML5Video
.
PlayerState
.
BUFFER
ING
;
_this
.
callStateChangeCallback
();
_this
.
callStateChangeCallback
();
},
false
);
},
false
);
this
.
video
.
addEventListener
(
'playing'
,
function
()
{
this
.
video
.
addEventListener
(
'playing'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
_this
.
callStateChangeCallback
();
},
false
);
},
false
);
// Register the 'pause' event.
// Register the 'pause' event.
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
450dfe17
...
@@ -17,13 +17,24 @@ function (HTML5Video, Resizer) {
...
@@ -17,13 +17,24 @@ function (HTML5Video, Resizer) {
methodsDict
=
{
methodsDict
=
{
duration
:
duration
,
duration
:
duration
,
handlePlaybackQualityChange
:
handlePlaybackQualityChange
,
handlePlaybackQualityChange
:
handlePlaybackQualityChange
,
// Added for finer graded seeking control.
// Please see:
// https://developers.google.com/youtube/js_api_reference#Events
isBuffering
:
isBuffering
,
// https://developers.google.com/youtube/js_api_reference#cueVideoById
isCued
:
isCued
,
isEnded
:
isEnded
,
isEnded
:
isEnded
,
isPlaying
:
isPlaying
,
isPlaying
:
isPlaying
,
isUnstarted
:
isUnstarted
,
log
:
log
,
log
:
log
,
onCaptionSeek
:
onSeek
,
onCaptionSeek
:
onSeek
,
onEnded
:
onEnded
,
onEnded
:
onEnded
,
onPause
:
onPause
,
onPause
:
onPause
,
onPlay
:
onPlay
,
onPlay
:
onPlay
,
runTimer
:
runTimer
,
stopTimer
:
stopTimer
,
onPlaybackQualityChange
:
onPlaybackQualityChange
,
onPlaybackQualityChange
:
onPlaybackQualityChange
,
onReady
:
onReady
,
onReady
:
onReady
,
onSlideSeek
:
onSeek
,
onSlideSeek
:
onSeek
,
...
@@ -54,7 +65,17 @@ function (HTML5Video, Resizer) {
...
@@ -54,7 +65,17 @@ function (HTML5Video, Resizer) {
// Functions which will be accessible via 'state' object. When called,
// Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context.
// these functions will get the 'state' object as a context.
function
_makeFunctionsPublic
(
state
)
{
function
_makeFunctionsPublic
(
state
)
{
var
debouncedF
=
_
.
debounce
(
function
(
params
)
{
return
onSeek
.
call
(
this
,
params
);
}.
bind
(
state
),
300
);
state
.
bindTo
(
methodsDict
,
state
.
videoPlayer
,
state
);
state
.
bindTo
(
methodsDict
,
state
.
videoPlayer
,
state
);
state
.
videoPlayer
.
onSlideSeek
=
debouncedF
;
state
.
videoPlayer
.
onCaptionSeek
=
debouncedF
;
}
}
// function _initialize(state)
// function _initialize(state)
...
@@ -79,10 +100,10 @@ function (HTML5Video, Resizer) {
...
@@ -79,10 +100,10 @@ function (HTML5Video, Resizer) {
state
.
videoPlayer
.
player
.
setVolume
(
state
.
currentVolume
);
state
.
videoPlayer
.
player
.
setVolume
(
state
.
currentVolume
);
});
});
if
(
state
.
videoType
===
'youtube'
)
{
if
(
state
.
isYoutubeType
()
)
{
state
.
videoPlayer
.
PlayerState
=
YT
.
PlayerState
;
state
.
videoPlayer
.
PlayerState
=
YT
.
PlayerState
;
state
.
videoPlayer
.
PlayerState
.
UNSTARTED
=
-
1
;
state
.
videoPlayer
.
PlayerState
.
UNSTARTED
=
-
1
;
}
else
{
// if (state.videoType === 'html5') {
}
else
{
state
.
videoPlayer
.
PlayerState
=
HTML5Video
.
PlayerState
;
state
.
videoPlayer
.
PlayerState
=
HTML5Video
.
PlayerState
;
}
}
...
@@ -139,7 +160,7 @@ function (HTML5Video, Resizer) {
...
@@ -139,7 +160,7 @@ function (HTML5Video, Resizer) {
_updateVcrAndRegion
(
state
);
_updateVcrAndRegion
(
state
);
},
false
);
},
false
);
}
else
{
// if (state.videoType === 'youtube') {
}
else
{
youTubeId
=
state
.
youtubeId
();
youTubeId
=
state
.
youtubeId
();
state
.
videoPlayer
.
player
=
new
YT
.
Player
(
state
.
id
,
{
state
.
videoPlayer
.
player
=
new
YT
.
Player
(
state
.
id
,
{
...
@@ -305,8 +326,8 @@ function (HTML5Video, Resizer) {
...
@@ -305,8 +326,8 @@ function (HTML5Video, Resizer) {
// This function gets the video's current play position in time
// This function gets the video's current play position in time
// (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
(
time
)
{
this
.
videoPlayer
.
currentTime
=
this
.
videoPlayer
.
player
.
getCurrentTime
();
this
.
videoPlayer
.
currentTime
=
t
ime
||
t
his
.
videoPlayer
.
player
.
getCurrentTime
();
if
(
isFinite
(
this
.
videoPlayer
.
currentTime
))
{
if
(
isFinite
(
this
.
videoPlayer
.
currentTime
))
{
this
.
videoPlayer
.
updatePlayTime
(
this
.
videoPlayer
.
currentTime
);
this
.
videoPlayer
.
updatePlayTime
(
this
.
videoPlayer
.
currentTime
);
...
@@ -339,7 +360,7 @@ function (HTML5Video, Resizer) {
...
@@ -339,7 +360,7 @@ function (HTML5Video, Resizer) {
!
(
!
(
this
.
browserIsFirefox
&&
this
.
browserIsFirefox
&&
newSpeed
===
'1.0'
&&
newSpeed
===
'1.0'
&&
this
.
videoType
===
'youtube'
this
.
isYoutubeType
()
)
)
)
{
)
{
this
.
videoPlayer
.
player
.
setPlaybackRate
(
newSpeed
);
this
.
videoPlayer
.
player
.
setPlaybackRate
(
newSpeed
);
...
@@ -404,6 +425,7 @@ function (HTML5Video, Resizer) {
...
@@ -404,6 +425,7 @@ function (HTML5Video, Resizer) {
return
;
return
;
}
}
this
.
el
.
off
(
'play.seek'
);
this
.
videoPlayer
.
log
(
this
.
videoPlayer
.
log
(
'seek_video'
,
'seek_video'
,
{
{
...
@@ -420,24 +442,49 @@ function (HTML5Video, Resizer) {
...
@@ -420,24 +442,49 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
stopAtEndTime
=
false
;
this
.
videoPlayer
.
stopAtEndTime
=
false
;
}
}
this
.
videoPlayer
.
player
.
seekTo
(
newTime
,
true
);
if
(
this
.
videoPlayer
.
isPlaying
())
{
if
(
this
.
videoPlayer
.
isPlaying
())
{
clearInterval
(
this
.
videoPlayer
.
updateInterval
);
this
.
videoPlayer
.
stopTimer
();
this
.
videoPlayer
.
updateInterval
=
setInterval
(
this
.
videoPlayer
.
update
,
200
);
setTimeout
(
this
.
videoPlayer
.
update
,
0
);
}
else
{
}
else
{
this
.
videoPlayer
.
currentTime
=
newTime
;
this
.
videoPlayer
.
currentTime
=
newTime
;
}
}
var
isUnplayed
=
this
.
videoPlayer
.
isUnstarted
()
||
this
.
videoPlayer
.
isCued
();
this
.
videoPlayer
.
updatePlayTime
(
newTime
);
// Use `cueVideoById` method for youtube video that is not played before.
if
(
isUnplayed
&&
this
.
isYoutubeType
())
{
this
.
videoPlayer
.
player
.
cueVideoById
(
this
.
youtubeId
(),
newTime
);
}
else
{
// Youtube video cannot be rewinded during bufferization, so wait to
// finish bufferization and then rewind the video.
if
(
this
.
isYoutubeType
()
&&
this
.
videoPlayer
.
isBuffering
())
{
this
.
el
.
on
(
'play.seek'
,
function
()
{
this
.
videoPlayer
.
player
.
seekTo
(
newTime
,
true
);
}.
bind
(
this
));
}
else
{
// Otherwise, just seek the video
this
.
videoPlayer
.
player
.
seekTo
(
newTime
,
true
);
}
}
this
.
videoPlayer
.
updatePlayTime
(
newTime
,
true
);
this
.
el
.
trigger
(
'seek'
,
arguments
);
this
.
el
.
trigger
(
'seek'
,
arguments
);
}
}
function
runTimer
()
{
if
(
!
this
.
videoPlayer
.
updateInterval
)
{
this
.
videoPlayer
.
updateInterval
=
window
.
setInterval
(
this
.
videoPlayer
.
update
,
200
);
this
.
videoPlayer
.
update
();
}
}
function
stopTimer
()
{
window
.
clearInterval
(
this
.
videoPlayer
.
updateInterval
);
delete
this
.
videoPlayer
.
updateInterval
;
}
function
onEnded
()
{
function
onEnded
()
{
var
time
=
this
.
videoPlayer
.
duration
();
var
time
=
this
.
videoPlayer
.
duration
();
...
@@ -466,8 +513,7 @@ function (HTML5Video, Resizer) {
...
@@ -466,8 +513,7 @@ function (HTML5Video, Resizer) {
}
}
);
);
clearInterval
(
this
.
videoPlayer
.
updateInterval
);
this
.
videoPlayer
.
stopTimer
();
delete
this
.
videoPlayer
.
updateInterval
;
this
.
trigger
(
'videoControl.pause'
,
null
);
this
.
trigger
(
'videoControl.pause'
,
null
);
this
.
saveState
(
true
);
this
.
saveState
(
true
);
...
@@ -482,14 +528,7 @@ function (HTML5Video, Resizer) {
...
@@ -482,14 +528,7 @@ function (HTML5Video, Resizer) {
}
}
);
);
if
(
!
this
.
videoPlayer
.
updateInterval
)
{
this
.
videoPlayer
.
runTimer
();
this
.
videoPlayer
.
updateInterval
=
setInterval
(
this
.
videoPlayer
.
update
,
200
);
this
.
videoPlayer
.
update
();
}
this
.
trigger
(
'videoControl.play'
,
null
);
this
.
trigger
(
'videoControl.play'
,
null
);
this
.
trigger
(
'videoProgressSlider.notifyThroughHandleEnd'
,
{
this
.
trigger
(
'videoProgressSlider.notifyThroughHandleEnd'
,
{
end
:
false
end
:
false
...
@@ -558,7 +597,7 @@ function (HTML5Video, Resizer) {
...
@@ -558,7 +597,7 @@ function (HTML5Video, Resizer) {
// https://github.com/edx/edx-platform/pull/2841
// https://github.com/edx/edx-platform/pull/2841
if
(
if
(
(
this
.
isHtml5Mode
()
||
availablePlaybackRates
.
length
>
1
)
&&
(
this
.
isHtml5Mode
()
||
availablePlaybackRates
.
length
>
1
)
&&
this
.
videoType
===
'youtube'
this
.
isYoutubeType
()
)
{
)
{
if
(
availablePlaybackRates
.
length
===
1
&&
!
this
.
isTouch
)
{
if
(
availablePlaybackRates
.
length
===
1
&&
!
this
.
isTouch
)
{
// This condition is needed in cases when Firefox version is
// This condition is needed in cases when Firefox version is
...
@@ -620,20 +659,34 @@ function (HTML5Video, Resizer) {
...
@@ -620,20 +659,34 @@ function (HTML5Video, Resizer) {
}
}
function
onStateChange
(
event
)
{
function
onStateChange
(
event
)
{
this
.
el
.
removeClass
([
'is-unstarted'
,
'is-playing'
,
'is-paused'
,
'is-buffered'
,
'is-ended'
,
'is-cued'
].
join
(
' '
));
switch
(
event
.
data
)
{
switch
(
event
.
data
)
{
case
this
.
videoPlayer
.
PlayerState
.
UNSTARTED
:
case
this
.
videoPlayer
.
PlayerState
.
UNSTARTED
:
this
.
el
.
addClass
(
'is-unstarted'
);
this
.
videoPlayer
.
onUnstarted
();
this
.
videoPlayer
.
onUnstarted
();
break
;
break
;
case
this
.
videoPlayer
.
PlayerState
.
PLAYING
:
case
this
.
videoPlayer
.
PlayerState
.
PLAYING
:
this
.
el
.
addClass
(
'is-playing'
);
this
.
videoPlayer
.
onPlay
();
this
.
videoPlayer
.
onPlay
();
break
;
break
;
case
this
.
videoPlayer
.
PlayerState
.
PAUSED
:
case
this
.
videoPlayer
.
PlayerState
.
PAUSED
:
this
.
el
.
addClass
(
'is-paused'
);
this
.
videoPlayer
.
onPause
();
this
.
videoPlayer
.
onPause
();
break
;
break
;
case
this
.
videoPlayer
.
PlayerState
.
BUFFERING
:
this
.
el
.
addClass
(
'is-buffered'
);
this
.
el
.
trigger
(
'buffering'
);
break
;
case
this
.
videoPlayer
.
PlayerState
.
ENDED
:
case
this
.
videoPlayer
.
PlayerState
.
ENDED
:
this
.
el
.
addClass
(
'is-ended'
);
this
.
videoPlayer
.
onEnded
();
this
.
videoPlayer
.
onEnded
();
break
;
break
;
case
this
.
videoPlayer
.
PlayerState
.
CUED
:
case
this
.
videoPlayer
.
PlayerState
.
CUED
:
this
.
el
.
addClass
(
'is-cued'
);
this
.
videoPlayer
.
player
.
seekTo
(
this
.
videoPlayer
.
seekToTimeOnCued
,
true
);
this
.
videoPlayer
.
player
.
seekTo
(
this
.
videoPlayer
.
seekToTimeOnCued
,
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
...
@@ -711,12 +764,12 @@ function (HTML5Video, Resizer) {
...
@@ -711,12 +764,12 @@ function (HTML5Video, Resizer) {
return
time
;
return
time
;
}
}
function
updatePlayTime
(
time
)
{
function
updatePlayTime
(
time
,
skip_seek
)
{
var
videoPlayer
=
this
.
videoPlayer
,
var
videoPlayer
=
this
.
videoPlayer
,
duration
=
this
.
videoPlayer
.
duration
(),
duration
=
this
.
videoPlayer
.
duration
(),
youTubeId
;
youTubeId
;
if
(
duration
>
0
&&
videoPlayer
.
goToStartTime
)
{
if
(
duration
>
0
&&
videoPlayer
.
goToStartTime
&&
!
skip_seek
)
{
videoPlayer
.
goToStartTime
=
false
;
videoPlayer
.
goToStartTime
=
false
;
// The duration might have changed. Update the start-end time region to
// The duration might have changed. Update the start-end time region to
...
@@ -746,7 +799,7 @@ function (HTML5Video, Resizer) {
...
@@ -746,7 +799,7 @@ function (HTML5Video, Resizer) {
//
//
// HTML5 video sources play fine from start-time in both Chrome
// HTML5 video sources play fine from start-time in both Chrome
// and Firefox.
// and Firefox.
if
(
this
.
browserIsFirefox
&&
this
.
videoType
===
'youtube'
)
{
if
(
this
.
browserIsFirefox
&&
this
.
isYoutubeType
()
)
{
youTubeId
=
this
.
youtubeId
();
youTubeId
=
this
.
youtubeId
();
// When we will call cueVideoById() for some strange reason
// When we will call cueVideoById() for some strange reason
...
@@ -794,10 +847,27 @@ function (HTML5Video, Resizer) {
...
@@ -794,10 +847,27 @@ function (HTML5Video, Resizer) {
}
}
function
isPlaying
()
{
function
isPlaying
()
{
var
playerState
=
this
.
videoPlayer
.
player
.
getPlayerState
(),
var
playerState
=
this
.
videoPlayer
.
player
.
getPlayerState
();
PLAYING
=
this
.
videoPlayer
.
PlayerState
.
PLAYING
;
return
playerState
===
this
.
videoPlayer
.
PlayerState
.
PLAYING
;
}
function
isBuffering
()
{
var
playerState
=
this
.
videoPlayer
.
player
.
getPlayerState
();
return
playerState
===
this
.
videoPlayer
.
PlayerState
.
BUFFERING
;
}
function
isCued
()
{
var
playerState
=
this
.
videoPlayer
.
player
.
getPlayerState
();
return
playerState
===
this
.
videoPlayer
.
PlayerState
.
CUED
;
}
function
isUnstarted
()
{
var
playerState
=
this
.
videoPlayer
.
player
.
getPlayerState
();
return
playerState
===
PLAYING
;
return
playerState
===
this
.
videoPlayer
.
PlayerState
.
UNSTARTED
;
}
}
/*
/*
...
@@ -844,7 +914,7 @@ function (HTML5Video, Resizer) {
...
@@ -844,7 +914,7 @@ function (HTML5Video, Resizer) {
// might differ by one or two seconds against the actual time as will
// might differ by one or two seconds against the actual time as will
// be reported later on by the player.getDuration() API function.
// be reported later on by the player.getDuration() API function.
if
(
!
isFinite
(
dur
)
||
dur
<=
0
)
{
if
(
!
isFinite
(
dur
)
||
dur
<=
0
)
{
if
(
this
.
videoType
===
'youtube'
)
{
if
(
this
.
isYoutubeType
()
)
{
dur
=
this
.
getDuration
();
dur
=
this
.
getDuration
();
}
}
}
}
...
@@ -873,9 +943,9 @@ function (HTML5Video, Resizer) {
...
@@ -873,9 +943,9 @@ function (HTML5Video, Resizer) {
});
});
}
}
if
(
this
.
videoType
===
'youtube'
)
{
if
(
this
.
isYoutubeType
()
)
{
logInfo
.
code
=
this
.
youtubeId
();
logInfo
.
code
=
this
.
youtubeId
();
}
else
if
(
this
.
videoType
===
'html5'
)
{
}
else
{
logInfo
.
code
=
'html5'
;
logInfo
.
code
=
'html5'
;
}
}
...
...
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
View file @
450dfe17
...
@@ -177,15 +177,26 @@ function () {
...
@@ -177,15 +177,26 @@ function () {
}
}
function
onSlide
(
event
,
ui
)
{
function
onSlide
(
event
,
ui
)
{
var
time
=
ui
.
value
,
duration
=
this
.
videoPlayer
.
duration
();
this
.
videoProgressSlider
.
frozen
=
true
;
this
.
videoProgressSlider
.
frozen
=
true
;
// Remember the seek to value so that we don't repeat ourselves on the
// Remember the seek to value so that we don't repeat ourselves on the
// 'stop' slider event.
// 'stop' slider event.
this
.
videoProgressSlider
.
lastSeekValue
=
ui
.
value
;
this
.
videoProgressSlider
.
lastSeekValue
=
time
;
this
.
trigger
(
'videoControl.updateVcrVidTime'
,
{
time
:
time
,
duration
:
duration
}
);
this
.
trigger
(
this
.
trigger
(
'videoPlayer.onSlideSeek'
,
'videoPlayer.onSlideSeek'
,
{
'type'
:
'onSlideSeek'
,
'time'
:
ui
.
valu
e
}
{
'type'
:
'onSlideSeek'
,
'time'
:
tim
e
}
);
);
// ARIA
// ARIA
...
...
lms/djangoapps/courseware/features/common.py
View file @
450dfe17
...
@@ -35,7 +35,7 @@ def configure_screenshots_for_all_steps(_step, action):
...
@@ -35,7 +35,7 @@ def configure_screenshots_for_all_steps(_step, action):
else
:
else
:
raise
ValueError
(
'Parameter `action` should be one of "enable" or "disable".'
)
raise
ValueError
(
'Parameter `action` should be one of "enable" or "disable".'
)
@world.absorb
def
capture_screenshot_before_after
(
func
):
def
capture_screenshot_before_after
(
func
):
"""
"""
A decorator that will take a screenshot before and after the applied
A decorator that will take a screenshot before and after the applied
...
...
lms/djangoapps/courseware/features/video.feature
View file @
450dfe17
...
@@ -2,18 +2,6 @@
...
@@ -2,18 +2,6 @@
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
# 1 Disabled 4/8/14 after intermittent failures in master
#Scenario: Video component stores position correctly when page is reloaded
# Given the course has a Video component in "Youtube" mode
# When the video has rendered in "Youtube" mode
# And I click video button "play"
# And I click video button "pause"
# Then I seek video to "10" seconds
# And I click video button "play"
# And I click video button "pause"
# And I reload the page with video
# Then I see video slider at "10" seconds
# 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
...
@@ -267,7 +255,106 @@ Feature: LMS.Video component
...
@@ -267,7 +255,106 @@ Feature: LMS.Video component
Then
the video has rendered in
"HTML5"
mode
Then
the video has rendered in
"HTML5"
mode
And
the video does not show the captions
And
the video does not show the captions
# 20 Disabled 4/8/14 after intermittent failures in master
# 20
Scenario
:
Start time works for Youtube video
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
start_time
|
|
00:00:10
|
And
I click video button
"play"
Then I see video slider at "0
:
10"
position
# 21
Scenario
:
End time works for Youtube video
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
end_time
|
|
00:00:02
|
And
I click video button
"play"
And
I wait
"5"
seconds
Then I see video slider at "0
:
02"
position
# 22
Scenario
:
Youtube video with end-time at 1
:
00 and the video starts playing at 0
:
58
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
end_time
|
|
00:01:00
|
And
I wait for video controls appear
And I seek video to "0
:
58"
position
And
I click video button
"play"
And
I wait
"5"
seconds
Then I see video slider at "1
:
00"
position
# 23
Scenario
:
Start time and end time work together for Youtube video
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
start_time
|
end_time
|
|
00:00:10
|
00:00:12
|
And
I click video button
"play"
Then I see video slider at "0
:
10"
position
And
I wait
"5"
seconds
Then I see video slider at "0
:
12"
position
# 24
Scenario
:
Youtube video after pausing at end time video plays to the end from end time
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
start_time
|
end_time
|
|
00:01:51
|
00:01:52
|
And
I click video button
"play"
And
I wait
"5"
seconds
# The end time is 00:01:52.
Then I see video slider at "1
:
52"
position
And
I click video button
"play"
And
I wait
"8"
seconds
# The default video length is 00:01:55.
Then I see video slider at "1
:
55"
position
# 25
Scenario
:
Youtube video with end-time at 0
:
32 and start-time at 0
:
30, the video starts playing from 0
:
28
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
start_time
|
end_time
|
|
00:00:30
|
00:00:32
|
And
I wait for video controls appear
And I seek video to "0
:
28"
position
And
I click video button
"play"
And
I wait
"8"
seconds
Then I see video slider at "0
:
32"
position
# 26
Scenario
:
Youtube video with end-time at 1
:
00, the video starts playing from 1
:
52
Given
I am registered for the course
"test_course"
And it has a video in "Youtube" mode
:
|
end_time
|
|
00:01:00
|
And
I wait for video controls appear
And I seek video to "1
:
52"
position
And
I click video button
"play"
And
I wait
"5"
seconds
# Video stops at the end.
Then I see video slider at "1
:
55"
position
# 27
@skip_firefox
Scenario
:
Quality button appears on play
Given
the course has a Video component in
"Youtube"
mode
Then
I see video button
"quality"
is hidden
And
I click video button
"play"
Then
I see video button
"quality"
is visible
# 28
@skip_firefox
Scenario
:
Quality button works correctly
Given
the course has a Video component in
"Youtube"
mode
And
I click video button
"play"
And
I see video button
"quality"
is inactive
And
I click video button
"quality"
Then
I see video button
"quality"
is active
# 29 Disabled 4/8/14 after intermittent failures in master
#Scenario: Transcripts are available on different speeds of Flash mode
#Scenario: Transcripts are available on different speeds of Flash mode
# Given I am registered for the course "test_course"
# Given I am registered for the course "test_course"
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
...
@@ -282,7 +369,7 @@ Feature: LMS.Video component
...
@@ -282,7 +369,7 @@ Feature: LMS.Video component
# Then I select the "1.25" speed
# Then I select the "1.25" speed
# And I see "Hi, welcome to Edx." text in the captions
# And I see "Hi, welcome to Edx." text in the captions
#
21
Disabled 4/8/14 after intermittent failures in master
#
30
Disabled 4/8/14 after intermittent failures in master
#Scenario: Elapsed time calculates correctly on different speeds of Flash mode
#Scenario: Elapsed time calculates correctly on different speeds of Flash mode
# Given I am registered for the course "test_course"
# Given I am registered for the course "test_course"
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
...
@@ -298,19 +385,14 @@ Feature: LMS.Video component
...
@@ -298,19 +385,14 @@ Feature: LMS.Video component
# And I click video button "pause"
# And I click video button "pause"
# And I click on caption line "2", video module shows elapsed time "4"
# And I click on caption line "2", video module shows elapsed time "4"
# 27
# 31 Disabled 4/8/14 after intermittent failures in master
@skip_firefox
#Scenario: Video component stores position correctly when page is reloaded
Scenario
:
Quality button appears on play
# Given the course has a Video component in "Youtube" mode
Given
the course has a Video component in
"Youtube"
mode
# When the video has rendered in "Youtube" mode
Then
I see video button
"quality"
is hidden
# And I click video button "play"
And
I click video button
"play"
# And I click video button "pause"
Then
I see video button
"quality"
is visible
# Then I seek video to "0:10" position
# And I click video button "play"
# 28
# And I click video button "pause"
@skip_firefox
# And I reload the page with video
Scenario
:
Quality button works correctly
# Then I see video slider at "0:10" position
Given
the course has a Video component in
"Youtube"
mode
And
I click video button
"play"
And
I see video button
"quality"
is inactive
And
I click video button
"quality"
Then
I see video button
"quality"
is active
lms/djangoapps/courseware/features/video.py
View file @
450dfe17
...
@@ -482,6 +482,7 @@ def check_captions(_step):
...
@@ -482,6 +482,7 @@ def check_captions(_step):
@step
(
'I select language with code "([^"]*)"$'
)
@step
(
'I select language with code "([^"]*)"$'
)
def
select_language
(
_step
,
code
):
def
select_language
(
_step
,
code
):
world
.
wait_for_visible
(
'.video-controls'
)
# Make sure that all ajax requests that affects the language menu are finished.
# Make sure that all ajax requests that affects the language menu are finished.
# For example, request to get new translation etc.
# For example, request to get new translation etc.
world
.
wait_for_ajax_complete
()
world
.
wait_for_ajax_complete
()
...
@@ -506,16 +507,19 @@ def select_language(_step, code):
...
@@ -506,16 +507,19 @@ def select_language(_step, code):
@step
(
'I click video button "([^"]*)"$'
)
@step
(
'I click video button "([^"]*)"$'
)
def
click_button
(
_step
,
button
):
def
click_button
(
_step
,
button
):
world
.
css_click
(
VIDEO_BUTTONS
[
button
])
world
.
css_click
(
VIDEO_BUTTONS
[
button
])
world
.
wait_for_ajax_complete
()
if
button
==
"play"
:
# Needs to wait for video buffrization
@step
(
'I see video slider at "([^"]*)" seconds$'
)
def
start_playing_video_from_n_seconds
(
_step
,
position
):
world
.
wait_for
(
world
.
wait_for
(
func
=
lambda
_
:
elapsed_time
()
>
0
,
func
=
lambda
_
:
world
.
css_has_class
(
'.video'
,
'is-playing'
)
and
world
.
is_css_present
(
VIDEO_BUTTONS
[
'pause'
])
,
timeout
=
30
timeout
=
30
)
)
world
.
wait_for_ajax_complete
()
@step
(
'I see video slider at "([^"]*)" position$'
)
def
start_playing_video_from_n_seconds
(
_step
,
time_str
):
position
=
parse_time_str
(
time_str
)
actual_position
=
elapsed_time
()
actual_position
=
elapsed_time
()
assert_equal
(
actual_position
,
int
(
position
),
"Current position is {}, but should be {}"
.
format
(
actual_position
,
position
))
assert_equal
(
actual_position
,
int
(
position
),
"Current position is {}, but should be {}"
.
format
(
actual_position
,
position
))
...
@@ -530,12 +534,21 @@ def i_see_duration(_step, position):
...
@@ -530,12 +534,21 @@ def i_see_duration(_step, position):
assert
duration
()
==
parse_time_str
(
position
)
assert
duration
()
==
parse_time_str
(
position
)
@step
(
'I seek video to "([^"]*)" seconds$'
)
@step
(
'I wait for video controls appear$'
)
def
seek_video_to_n_seconds
(
_step
,
seconds
):
def
controls_appear
(
_step
):
time
=
float
(
seconds
.
strip
())
world
.
wait_for_visible
(
'.video-controls'
)
jsCode
=
"$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0:f}}})"
.
format
(
time
)
@step
(
'I seek video to "([^"]*)" position$'
)
def
seek_video_to_n_seconds
(
_step
,
time_str
):
time
=
parse_time_str
(
time_str
)
jsCode
=
"$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0}}})"
.
format
(
time
)
world
.
browser
.
execute_script
(
jsCode
)
world
.
browser
.
execute_script
(
jsCode
)
_step
.
given
(
'I see video slider at "{}" seconds'
.
format
(
seconds
))
world
.
wait_for
(
func
=
lambda
_
:
world
.
retry_on_exception
(
lambda
:
elapsed_time
()
==
time
and
not
world
.
css_has_class
(
'.video'
,
'is-buffering'
)),
timeout
=
30
)
_step
.
given
(
'I see video slider at "{0}" position'
.
format
(
time_str
))
@step
(
'I have a "([^"]*)" transcript file in assets$'
)
@step
(
'I have a "([^"]*)" transcript file in assets$'
)
...
...
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