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
a09e6104
Commit
a09e6104
authored
Aug 30, 2013
by
Anton Stupak
Committed by
Alexander Kryklia
Sep 03, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix multiple video bug
parent
14708aa3
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
463 additions
and
92 deletions
+463
-92
common/lib/xmodule/xmodule/css/video/display.scss
+27
-0
common/lib/xmodule/xmodule/js/fixtures/video.html
+2
-0
common/lib/xmodule/xmodule/js/fixtures/video_all.html
+3
-2
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+3
-2
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
+2
-0
common/lib/xmodule/xmodule/js/spec/helper.coffee
+14
-2
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
+16
-44
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+89
-27
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+14
-4
common/lib/xmodule/xmodule/js/src/video/10_main.js
+10
-1
common/lib/xmodule/xmodule/video_module.py
+11
-1
lms/djangoapps/courseware/features/video.feature
+27
-6
lms/djangoapps/courseware/features/video.py
+45
-0
lms/djangoapps/courseware/features/youtube_setup.py
+45
-0
lms/djangoapps/courseware/mock_youtube_server/__init__.py
+0
-0
lms/djangoapps/courseware/mock_youtube_server/mock_youtube_server.py
+81
-0
lms/djangoapps/courseware/mock_youtube_server/test_mock_youtube_server.py
+53
-0
lms/djangoapps/courseware/tests/test_video_mongo.py
+6
-2
lms/djangoapps/courseware/tests/test_video_xml.py
+3
-1
lms/envs/acceptance.py
+5
-0
lms/envs/acceptance_static.py
+4
-0
lms/templates/video.html
+3
-0
No files found.
common/lib/xmodule/xmodule/css/video/display.scss
View file @
a09e6104
...
...
@@ -40,6 +40,12 @@ div.video {
padding-bottom
:
56
.25%
;
position
:
relative
;
div
{
&
.hidden
{
display
:
none
;
}
}
object
,
iframe
{
border
:
none
;
height
:
100%
;
...
...
@@ -48,6 +54,15 @@ div.video {
top
:
0
;
width
:
100%
;
}
h3
{
text-align
:
center
;
color
:
white
;
&
.hidden
{
display
:
none
;
}
}
}
section
.video-controls
{
...
...
@@ -516,6 +531,12 @@ div.video {
height
:
0px
;
}
article
.video-wrapper
section
.video-player
{
h3
{
color
:
black
;
}
}
ol
.subtitles
{
width
:
0
;
height
:
0
;
...
...
@@ -563,6 +584,12 @@ div.video {
position
:
static
;
}
article
.video-wrapper
section
.video-player
{
h3
{
color
:
white
;
}
}
div
.tc-wrapper
{
@include
clearfix
;
display
:
table
;
...
...
common/lib/xmodule/xmodule/js/fixtures/video.html
View file @
a09e6104
...
...
@@ -10,6 +10,8 @@
data-end=
""
data-caption-asset-path=
"/static/subs/"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-url=
"https://gdata.youtube.com/feeds/api/videos/"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_all.html
View file @
a09e6104
...
...
@@ -13,6 +13,8 @@
data-webm-source=
"test_files/test.webm"
data-ogg-source=
"test_files/test.ogv"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-url=
"https://gdata.youtube.com/feeds/api/videos/"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
...
...
@@ -55,4 +57,4 @@
</div>
</div>
</div>
</div>
\ No newline at end of file
</div>
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
View file @
a09e6104
...
...
@@ -13,6 +13,8 @@
data-webm-source=
"test_files/test.webm"
data-ogg-source=
"test_files/test.ogv"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-url=
"https://gdata.youtube.com/feeds/api/videos/"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
...
...
@@ -27,4 +29,4 @@
</div>
</div>
</div>
</div>
\ No newline at end of file
</div>
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
View file @
a09e6104
...
...
@@ -10,6 +10,8 @@
data-end=
""
data-caption-asset-path=
"/static/subs/"
data-autoplay=
"False"
data-yt-test-timeout=
"1500"
data-yt-test-url=
"https://gdata.youtube.com/feeds/api/videos/"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
...
...
common/lib/xmodule/xmodule/js/spec/helper.coffee
View file @
a09e6104
...
...
@@ -90,12 +90,24 @@ jasmine.stubbedHtml5Speeds = ['0.75', '1.0', '1.25', '1.50']
jasmine
.
stubRequests
=
->
spyOn
(
$
,
'ajax'
).
andCallFake
(
settings
)
->
if
match
=
settings
.
url
.
match
/youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/
if
settings
.
success
status
=
match
[
1
].
split
(
'_'
)
if
status
and
status
[
0
]
is
'status'
{
always
:
(
callback
)
->
callback
.
call
(
window
,
{},
status
[
1
])
error
:
(
callback
)
->
callback
.
call
(
window
,
{},
status
[
1
])
done
:
(
callback
)
->
callback
.
call
(
window
,
{},
status
[
1
])
}
else
if
settings
.
success
# match[1] - it's video ID
settings
.
success
data
:
jasmine
.
stubbedMetadata
[
match
[
1
]]
else
{
always
:
(
callback
)
->
callback
.
call
(
window
,
{},
'success'
);
callback
.
call
(
window
,
{},
'success'
)
done
:
(
callback
)
->
callback
.
call
(
window
,
{},
'success'
)
}
else
if
match
=
settings
.
url
.
match
/static(\/.*)?\/subs\/(.+)\.srt\.sjson/
settings
.
success
jasmine
.
stubbedCaption
...
...
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
View file @
a09e6104
...
...
@@ -55,46 +55,6 @@
expect
(
this
.
state
.
speed
).
toEqual
(
'0.75'
);
});
});
describe
(
'Check Youtube link existence'
,
function
()
{
var
statusList
=
{
error
:
'html5'
,
timeout
:
'html5'
,
abort
:
'html5'
,
parsererror
:
'html5'
,
success
:
'youtube'
,
notmodified
:
'youtube'
};
function
stubDeffered
(
data
,
status
)
{
return
{
always
:
function
(
callback
)
{
callback
.
call
(
window
,
data
,
status
);
}
}
}
function
checkPlayer
(
videoType
,
data
,
status
)
{
this
.
state
=
new
window
.
Video
(
'#example'
);
spyOn
(
this
.
state
,
'getVideoMetadata'
)
.
andReturn
(
stubDeffered
(
data
,
status
));
this
.
state
.
initialize
(
'#example'
);
expect
(
this
.
state
.
videoType
).
toEqual
(
videoType
);
}
it
(
'if video id is incorrect'
,
function
()
{
checkPlayer
(
'html5'
,
{
error
:
{}
},
'success'
);
});
$
.
each
(
statusList
,
function
(
status
,
mode
){
it
(
'Status:'
+
status
+
', mode:'
+
mode
,
function
()
{
checkPlayer
(
mode
,
{},
status
);
});
});
});
});
describe
(
'HTML5'
,
function
()
{
...
...
@@ -154,10 +114,22 @@
it
(
'parse Html5 sources'
,
function
()
{
var
html5Sources
=
{
mp4
:
'test_files/test.mp4'
,
webm
:
'test_files/test.webm'
,
ogg
:
'test_files/test.ogv'
};
mp4
:
null
,
webm
:
null
,
ogg
:
null
},
v
=
document
.
createElement
(
'video'
);
if
(
!!
(
v
.
canPlayType
&&
v
.
canPlayType
(
'video/webm; codecs="vp8, vorbis"'
).
replace
(
/no/
,
''
)))
{
html5Sources
[
'webm'
]
=
'xmodule/include/fixtures/test.webm'
;
}
if
(
!!
(
v
.
canPlayType
&&
v
.
canPlayType
(
'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
).
replace
(
/no/
,
''
)))
{
html5Sources
[
'mp4'
]
=
'xmodule/include/fixtures/test.mp4'
;
}
if
(
!!
(
v
.
canPlayType
&&
v
.
canPlayType
(
'video/ogg; codecs="theora"'
).
replace
(
/no/
,
''
)))
{
html5Sources
[
'ogg'
]
=
'xmodule/include/fixtures/test.ogv'
;
}
expect
(
state
.
html5Sources
).
toEqual
(
html5Sources
);
});
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
a09e6104
...
...
@@ -143,8 +143,6 @@ function (VideoPlayer) {
if
(
state
.
parseYoutubeStreams
(
state
.
config
.
youtubeStreams
))
{
state
.
videoType
=
'youtube'
;
state
.
fetchMetadata
();
state
.
parseSpeed
();
return
true
;
}
return
false
;
...
...
@@ -153,9 +151,7 @@ function (VideoPlayer) {
// function _prepareHTML5Video(state)
// The function prepare HTML5 video, parse HTML5
// video sources etc.
function
_prepareHTML5Video
(
state
)
{
state
.
videoType
=
'html5'
;
function
_prepareHTML5Video
(
state
,
html5Mode
)
{
state
.
parseVideoSources
(
{
mp4
:
state
.
config
.
mp4Source
,
...
...
@@ -164,20 +160,39 @@ function (VideoPlayer) {
}
);
if
(
html5Mode
)
{
state
.
speeds
=
[
'0.75'
,
'1.0'
,
'1.25'
,
'1.50'
];
state
.
videos
=
{
'0.75'
:
state
.
config
.
sub
,
'1.0'
:
state
.
config
.
sub
,
'1.25'
:
state
.
config
.
sub
,
'1.5'
:
state
.
config
.
sub
};
}
// We must have at least one non-YouTube video source available.
// Otherwise, return a negative.
if
(
state
.
html5Sources
.
webm
===
null
&&
state
.
html5Sources
.
mp4
===
null
&&
state
.
html5Sources
.
ogg
===
null
)
{
state
.
el
.
find
(
'.video-player div'
).
addClass
(
'hidden'
);
state
.
el
.
find
(
'.video-player h3'
).
removeClass
(
'hidden'
);
return
false
;
}
state
.
videoType
=
'html5'
;
if
(
!
state
.
config
.
sub
||
!
state
.
config
.
sub
.
length
)
{
state
.
config
.
sub
=
''
;
state
.
config
.
show_captions
=
false
;
}
state
.
speeds
=
[
'0.75'
,
'1.0'
,
'1.25'
,
'1.50'
];
state
.
videos
=
{
'0.75'
:
state
.
config
.
sub
,
'1.0'
:
state
.
config
.
sub
,
'1.25'
:
state
.
config
.
sub
,
'1.5'
:
state
.
config
.
sub
};
state
.
setSpeed
(
$
.
cookie
(
'video_speed'
));
return
true
;
}
function
_setConfigurations
(
state
)
{
...
...
@@ -201,7 +216,7 @@ function (VideoPlayer) {
// The function set initial configuration and preparation.
function
initialize
(
element
)
{
var
_this
=
this
;
var
_this
=
this
,
tempYtTestTimeout
;
// This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'.
this
.
isFullScreen
=
false
;
...
...
@@ -227,28 +242,61 @@ function (VideoPlayer) {
webmSource
:
this
.
el
.
data
(
'webm-source'
),
oggSource
:
this
.
el
.
data
(
'ogg-source'
),
ytTestUrl
:
this
.
el
.
data
(
'yt-test-url'
),
fadeOutTimeout
:
1400
,
availableQualities
:
[
'hd720'
,
'hd1080'
,
'highres'
]
};
// Check if the YT test timeout has been set. If not, or it is in
// improper format, then set to default value.
tempYtTestTimeout
=
parseInt
(
this
.
el
.
data
(
'yt-test-timeout'
),
10
);
if
(
!
isFinite
(
tempYtTestTimeout
))
{
tempYtTestTimeout
=
1500
;
}
this
.
config
.
ytTestTimeout
=
tempYtTestTimeout
;
if
(
!
(
_parseYouTubeIDs
(
this
)))
{
// If we do not have YouTube ID's, try parsing HTML5 video sources.
_prepareHTML5Video
(
this
);
if
(
!
_prepareHTML5Video
(
this
,
true
))
{
// Non-YouTube sources were not found either.
return
;
}
_setConfigurations
(
this
);
_renderElements
(
this
);
}
else
{
this
.
getVideoMetadata
()
if
(
!
this
.
youtubeXhr
)
{
this
.
youtubeXhr
=
this
.
getVideoMetadata
();
}
this
.
youtubeXhr
.
always
(
function
(
json
,
status
)
{
var
err
=
$
.
isPlainObject
(
json
.
error
)
||
(
status
!==
"success"
&&
status
!==
"notmodified"
);
if
(
err
){
(
status
!==
'success'
&&
status
!==
'notmodified'
);
if
(
err
)
{
// When the youtube link doesn't work for any reason
// (for example, the great firewall in china) any
// alternate sources should automatically play.
_prepareHTML5Video
(
_this
);
_this
.
el
.
find
(
'a.quality_control'
).
hide
();
if
(
!
_prepareHTML5Video
(
_this
))
{
// Non-YouTube sources were not found either.
_this
.
el
.
find
(
'.video-player div'
).
removeClass
(
'hidden'
);
_this
.
el
.
find
(
'.video-player h3'
).
addClass
(
'hidden'
);
// If in reality the timeout was to short, try to
// continue loading the YouTube video anyways.
_this
.
fetchMetadata
();
_this
.
parseSpeed
();
}
else
{
// In-browser HTML5 player does not support quality
// control.
_this
.
el
.
find
(
'a.quality_control'
).
hide
();
}
}
else
{
_this
.
fetchMetadata
();
_this
.
parseSpeed
();
}
_setConfigurations
(
_this
);
...
...
@@ -294,7 +342,13 @@ function (VideoPlayer) {
// Take the HTML5 sources (URLs of videos), and make them available explictly for each type
// of video format (mp4, webm, ogg).
function
parseVideoSources
(
sources
)
{
var
_this
=
this
;
var
_this
=
this
,
v
=
document
.
createElement
(
'video'
),
sourceCodecs
=
{
mp4
:
'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
,
webm
:
'video/webm; codecs="vp8, vorbis"'
,
ogg
:
'video/ogg; codecs="theora"'
};
this
.
html5Sources
=
{
mp4
:
null
,
...
...
@@ -304,7 +358,14 @@ function (VideoPlayer) {
$
.
each
(
sources
,
function
(
name
,
source
)
{
if
(
source
&&
source
.
length
)
{
_this
.
html5Sources
[
name
]
=
source
;
if
(
Boolean
(
v
.
canPlayType
&&
v
.
canPlayType
(
sourceCodecs
[
name
]).
replace
(
/no/
,
''
)
)
)
{
_this
.
html5Sources
[
name
]
=
source
;
}
}
});
}
...
...
@@ -321,7 +382,9 @@ function (VideoPlayer) {
$
.
each
(
this
.
videos
,
function
(
speed
,
url
)
{
_this
.
getVideoMetadata
(
url
,
function
(
data
)
{
_this
.
metadata
[
data
.
data
.
id
]
=
data
.
data
;
if
(
data
.
data
)
{
_this
.
metadata
[
data
.
data
.
id
]
=
data
.
data
;
}
});
});
}
...
...
@@ -358,12 +421,11 @@ function (VideoPlayer) {
if
(
typeof
url
!==
'string'
)
{
url
=
this
.
videos
[
'1.0'
]
||
''
;
}
successHandler
=
(
$
.
isFunction
(
callback
))
?
callback
:
null
;
xhr
=
$
.
ajax
({
url
:
'https://gdata.youtube.com/feeds/api/videos/'
+
url
+
'?v=2&alt=jsonc'
,
timeout
:
500
,
url
:
this
.
config
.
ytTestUrl
+
url
+
'?v=2&alt=jsonc'
,
dataType
:
'jsonp'
,
timeout
:
this
.
config
.
ytTestTimeout
,
success
:
successHandler
});
...
...
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
a09e6104
...
...
@@ -10,21 +10,31 @@ function () {
return
function
(
state
)
{
state
.
videoSpeedControl
=
{};
if
(
state
.
videoType
===
'html5'
)
{
_initialize
(
state
);
}
else
if
(
state
.
videoType
===
'youtube'
&&
state
.
youtubeXhr
)
{
state
.
youtubeXhr
.
done
(
function
()
{
_initialize
(
state
);
});
}
if
(
state
.
videoType
===
'html5'
&&
!
(
_checkPlaybackRates
()))
{
_hideSpeedControl
(
state
);
return
;
}
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
function
_initialize
(
state
)
{
_makeFunctionsPublic
(
state
);
_renderElements
(
state
);
_bindHandlers
(
state
);
}
// function _makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called,
...
...
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
a09e6104
...
...
@@ -20,7 +20,8 @@ function (
VideoSpeedControl
,
VideoCaption
)
{
var
previousState
;
var
previousState
,
youtubeXhr
=
null
;
// Because this constructor can be called multiple times on a single page (when
// the user switches verticals, the page doesn't reload, but the content changes), we must
...
...
@@ -53,7 +54,11 @@ function (
state
=
{};
previousState
=
state
;
state
.
youtubeXhr
=
youtubeXhr
;
Initialize
(
state
,
element
);
if
(
!
youtubeXhr
)
{
youtubeXhr
=
state
.
youtubeXhr
;
}
VideoControl
(
state
);
VideoQualityControl
(
state
);
...
...
@@ -67,6 +72,10 @@ function (
// Video with Jasmine.
return
state
;
};
window
.
Video
.
clearYoutubeXhr
=
function
()
{
youtubeXhr
=
null
;
};
});
}(
RequireJS
.
requirejs
,
RequireJS
.
require
,
RequireJS
.
define
));
common/lib/xmodule/xmodule/video_module.py
View file @
a09e6104
...
...
@@ -167,6 +167,12 @@ class VideoModule(VideoFields, XModule):
sources
=
{
get_ext
(
src
):
src
for
src
in
self
.
html5_sources
}
sources
[
'main'
]
=
self
.
source
# for testing Youtube timeout in acceptance tests
if
getattr
(
settings
,
'VIDEO_PORT'
,
None
):
yt_test_url
=
"http://127.0.0.1:"
+
str
(
settings
.
VIDEO_PORT
)
+
'/test_youtube/'
else
:
yt_test_url
=
'https://gdata.youtube.com/feeds/api/videos/'
return
self
.
system
.
render_template
(
'video.html'
,
{
'youtube_streams'
:
_create_youtube_string
(
self
),
'id'
:
self
.
location
.
html_id
(),
...
...
@@ -181,7 +187,11 @@ class VideoModule(VideoFields, XModule):
'show_captions'
:
json
.
dumps
(
self
.
show_captions
),
'start'
:
self
.
start_time
,
'end'
:
self
.
end_time
,
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
)
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
# TODO: Later on the value 1500 should be taken from some global
# configuration setting field.
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
yt_test_url
})
...
...
lms/djangoapps/courseware/features/video.feature
View file @
a09e6104
Feature
:
Video component
As a student, I want to view course videos in LMS.
Scenario
:
Video component is fully rendered in the LMS in HTML5 mode
Given
the course has a Video component in HTML5 mode
Then
when I view the video it has rendered in HTML5 mode
And
all sources are correct
Scenario
:
Video component is fully rendered in the LMS in Youtube mode
Given
the course has a Video component in Youtube mode
Then
when I view the video it has rendered in Youtube mode
# Firefox doesn't have HTML5
# Firefox doesn't have HTML5 (only mp4 - fix here)
@skip_firefox
Scenario
:
Autoplay is enabled in LMS for a Video component
Given
the course has a Video component in HTML5 mode
Then
when I view the video it has autoplay enabled
# Youtube testing
Scenario
:
Video component is fully rendered in the LMS in Youtube mode with HTML5 sources
Given
youtube server is up and response time is 0.4 seconds
And
the course has a Video component in Youtube_HTML5 mode
Then
when I view the video it has rendered in Youtube mode
Scenario
:
Video component is not rendered in the LMS in Youtube mode with HTML5 sources
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube_HTML5 mode
Then
when I view the video it has rendered in HTML5 mode
Scenario
:
Video component is rendered in the LMS in Youtube mode without HTML5 sources
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube mode
Then
when I view the video it has rendered in Youtube mode
Scenario
:
Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube_HTML5_Unsupported_Video mode
Then
when I view the video it has rendered in Youtube mode
Scenario
:
Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser
Given
the course has a Video component in HTML5_Unsupported_Video mode
Then
error message is shown
And
error message has correct text
lms/djangoapps/courseware/features/video.py
View file @
a09e6104
...
...
@@ -3,6 +3,7 @@
from
lettuce
import
world
,
step
from
lettuce.django
import
django_url
from
common
import
i_am_registered_for_the_course
,
section_location
from
django.utils.translation
import
ugettext
as
_
############### ACTIONS ####################
...
...
@@ -11,6 +12,9 @@ HTML5_SOURCES = [
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.webm'
,
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.ogv'
]
HTML5_SOURCES_INCORRECT
=
[
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp99'
]
@step
(
'when I view the (.*) it has autoplay enabled$'
)
def
does_autoplay_video
(
_step
,
video_type
):
...
...
@@ -51,10 +55,37 @@ def add_video_to_course(course, player_mode):
'html5_sources'
:
HTML5_SOURCES
}
})
if
player_mode
==
'youtube_html5'
:
kwargs
.
update
({
'metadata'
:
{
'html5_sources'
:
HTML5_SOURCES
}
})
if
player_mode
==
'youtube_html5_unsupported_video'
:
kwargs
.
update
({
'metadata'
:
{
'html5_sources'
:
HTML5_SOURCES_INCORRECT
}
})
if
player_mode
==
'html5_unsupported_video'
:
kwargs
.
update
({
'metadata'
:
{
'youtube_id_1_0'
:
''
,
'youtube_id_0_75'
:
''
,
'youtube_id_1_25'
:
''
,
'youtube_id_1_5'
:
''
,
'html5_sources'
:
HTML5_SOURCES_INCORRECT
}
})
world
.
ItemFactory
.
create
(
**
kwargs
)
@step
(
'youtube server is up and response time is (.*) seconds$'
)
def
set_youtube_response_timeout
(
_step
,
time
):
world
.
youtube_server
.
time_to_response
=
time
@step
(
'when I view the video it has rendered in (.*) mode$'
)
def
video_is_rendered
(
_step
,
mode
):
modes
=
{
...
...
@@ -64,9 +95,23 @@ def video_is_rendered(_step, mode):
html_tag
=
modes
[
mode
.
lower
()]
assert
world
.
css_find
(
'.video {0}'
.
format
(
html_tag
))
.
first
@step
(
'all sources are correct$'
)
def
all_sources_are_correct
(
_step
):
sources
=
world
.
css_find
(
'.video video source'
)
assert
set
(
source
[
'src'
]
for
source
in
sources
)
==
set
(
HTML5_SOURCES
)
@step
(
'error message is shown$'
)
def
error_message_is_shown
(
_step
):
selector
=
'.video .video-player h3'
assert
world
.
css_visible
(
selector
)
@step
(
'error message has correct text$'
)
def
error_message_has_correct_text
(
_step
):
selector
=
'.video .video-player h3'
text
=
_
(
'ERROR: No playable video sources found!'
)
assert
world
.
css_has_text
(
selector
,
text
)
lms/djangoapps/courseware/features/youtube_setup.py
0 → 100644
View file @
a09e6104
#pylint: disable=C0111
#pylint: disable=W0621
from
courseware.mock_youtube_server.mock_youtube_server
import
MockYoutubeServer
from
lettuce
import
before
,
after
,
world
from
django.conf
import
settings
import
threading
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
@before.all
def
setup_mock_youtube_server
():
# import ipdb; ipdb.set_trace()
server_host
=
'127.0.0.1'
server_port
=
settings
.
VIDEO_PORT
address
=
(
server_host
,
server_port
)
# Create the mock server instance
server
=
MockYoutubeServer
(
address
)
logger
.
debug
(
"Youtube server started at {} port"
.
format
(
str
(
server_port
)))
server
.
time_to_response
=
1
# seconds
# Start the server running in a separate daemon thread
# Because the thread is a daemon, it will terminate
# when the main thread terminates.
server_thread
=
threading
.
Thread
(
target
=
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
# Store the server instance in lettuce's world
# so that other steps can access it
# (and we can shut it down later)
world
.
youtube_server
=
server
@after.all
def
teardown_mock_youtube_server
(
total
):
# Stop the LTI server and free up the port
world
.
youtube_server
.
shutdown
()
lms/djangoapps/courseware/mock_youtube_server/__init__.py
0 → 100644
View file @
a09e6104
lms/djangoapps/courseware/mock_youtube_server/mock_youtube_server.py
0 → 100644
View file @
a09e6104
from
BaseHTTPServer
import
HTTPServer
,
BaseHTTPRequestHandler
import
urlparse
from
requests.packages.oauthlib.oauth1.rfc5849
import
signature
import
mock
import
threading
import
json
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
import
time
class
MockYoutubeRequestHandler
(
BaseHTTPRequestHandler
):
'''
A handler for Youtube GET requests.
'''
protocol
=
"HTTP/1.0"
def
do_HEAD
(
self
):
self
.
_send_head
()
def
do_GET
(
self
):
'''
Handle a GET request from the client and sends response back.
'''
self
.
_send_head
()
logger
.
debug
(
"Youtube provider received GET request to path {}"
.
format
(
self
.
path
)
)
# Log the request
status_message
=
"I'm youtube."
response_timeout
=
float
(
self
.
server
.
time_to_response
)
# threading timer produces TypeError: 'NoneType' object is not callable here
# so we use time.sleep, as we already in separate thread.
time
.
sleep
(
response_timeout
)
self
.
_send_response
(
status_message
)
def
_send_head
(
self
):
'''
Send the response code and MIME headers
'''
self
.
send_response
(
200
)
self
.
send_header
(
'Content-type'
,
'text/html'
)
self
.
end_headers
()
def
_send_response
(
self
,
message
):
'''
Send message back to the client
'''
callback
=
urlparse
.
parse_qs
(
self
.
path
)[
'callback'
][
0
]
response
=
callback
+
'({})'
.
format
(
json
.
dumps
({
'message'
:
message
}))
# Log the response
logger
.
debug
(
"Youtube: sent response {}"
.
format
(
message
))
self
.
wfile
.
write
(
response
)
class
MockYoutubeServer
(
HTTPServer
):
'''
A mock Youtube provider server that responds
to GET requests to localhost.
'''
def
__init__
(
self
,
address
):
'''
Initialize the mock XQueue server instance.
*address* is the (host, host's port to listen to) tuple.
'''
handler
=
MockYoutubeRequestHandler
HTTPServer
.
__init__
(
self
,
address
,
handler
)
def
shutdown
(
self
):
'''
Stop the server and free up the port
'''
# First call superclass shutdown()
HTTPServer
.
shutdown
(
self
)
# We also need to manually close the socket
self
.
socket
.
close
()
lms/djangoapps/courseware/mock_youtube_server/test_mock_youtube_server.py
0 → 100644
View file @
a09e6104
"""
Test for Mock_Youtube_Server
"""
import
unittest
import
threading
import
urllib
from
mock_youtube_server
import
MockYoutubeServer
from
nose.plugins.skip
import
SkipTest
class
MockYoutubeServerTest
(
unittest
.
TestCase
):
'''
A mock version of the Youtube provider server that listens on a local
port and responds with jsonp.
Used for lettuce BDD tests in lms/courseware/features/video.feature
'''
def
setUp
(
self
):
# This is a test of the test setup,
# so it does not need to run as part of the unit test suite
# You can re-enable it by commenting out the line below
raise
SkipTest
# Create the server
server_port
=
8034
server_host
=
'127.0.0.1'
address
=
(
server_host
,
server_port
)
self
.
server
=
MockYoutubeServer
(
address
,
)
self
.
server
.
time_to_response
=
0.5
# Start the server in a separate daemon thread
server_thread
=
threading
.
Thread
(
target
=
self
.
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
def
tearDown
(
self
):
# Stop the server, freeing up the port
self
.
server
.
shutdown
()
def
test_request
(
self
):
"""
Tests that Youtube server processes request with right program
path, and responses with incorrect signature.
"""
# GET request
response_handle
=
urllib
.
urlopen
(
'http://127.0.0.1:8034/feeds/api/videos/OEoXaMPEzfM?v=2&alt=jsonc&callback=callback_func'
,
)
response
=
response_handle
.
read
()
self
.
assertEqual
(
"""callback_func({"message": "I
\'
m youtube."})"""
,
response
)
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
a09e6104
...
...
@@ -64,7 +64,9 @@ class TestVideo(BaseTestXmodule):
'sub'
:
u'a_sub_file.srt.sjson'
,
'track'
:
''
,
'youtube_streams'
:
_create_youtube_string
(
self
.
item_module
),
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
)
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
}
self
.
maxDiff
=
None
...
...
@@ -114,7 +116,9 @@ class TestVideoNonYouTube(TestVideo):
'sub'
:
'a_sub_file.srt.sjson'
,
'track'
:
''
,
'youtube_streams'
:
'1.00:OEoXaMPEzfM'
,
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
)
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
}
self
.
assertEqual
(
context
,
expected_context
)
lms/djangoapps/courseware/tests/test_video_xml.py
View file @
a09e6104
...
...
@@ -92,7 +92,9 @@ class VideoModuleUnitTest(unittest.TestCase):
'sources'
:
sources
,
'youtube_streams'
:
_create_youtube_string
(
module
),
'track'
:
''
,
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
)
'autoplay'
:
settings
.
MITX_FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
}
self
.
assertEqual
(
module
.
get_html
(),
expected_context
)
...
...
lms/envs/acceptance.py
View file @
a09e6104
...
...
@@ -82,6 +82,11 @@ XQUEUE_INTERFACE = {
"basic_auth"
:
(
'anant'
,
'agarwal'
),
}
# Set up Video information so that the lms will send
# requests to a mock Youtube server running locally
VIDEO_PORT
=
XQUEUE_PORT
+
2
# Forums are disabled in test.py to speed up unit tests, but we do not have
# per-test control for acceptance tests
MITX_FEATURES
[
'ENABLE_DISCUSSION_SERVICE'
]
=
True
...
...
lms/envs/acceptance_static.py
View file @
a09e6104
...
...
@@ -70,6 +70,10 @@ XQUEUE_INTERFACE = {
"basic_auth"
:
(
'anant'
,
'agarwal'
),
}
# Set up Video information so that the lms will send
# requests to a mock Youtube server running locally
VIDEO_PORT
=
XQUEUE_PORT
+
2
# Include the lettuce app for acceptance testing, including the 'harvest' django-admin command
INSTALLED_APPS
+=
(
'lettuce.django'
,)
LETTUCE_APPS
=
(
'courseware'
,)
...
...
lms/templates/video.html
View file @
a09e6104
...
...
@@ -23,6 +23,8 @@
data-end=
"${end}"
data-caption-asset-path=
"${caption_asset_path}"
data-autoplay=
"${autoplay}"
data-yt-test-timeout=
"${yt_test_timeout}"
data-yt-test-url=
"${yt_test_url}"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
...
...
@@ -30,6 +32,7 @@
<section
class=
"video-player"
>
<div
id=
"${id}"
></div>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
</section>
<div
class=
"video-player-post"
></div>
...
...
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