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
3cc793b0
Commit
3cc793b0
authored
Sep 03, 2013
by
Adam
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #856 from edx/adam/merge-conflicts
Adam/merge conflicts
parents
e284ac68
67890b3c
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
458 additions
and
89 deletions
+458
-89
cms/templates/import.html
+1
-1
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
+2
-0
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+2
-0
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/templates/video.html
+3
-0
No files found.
cms/templates/import.html
View file @
3cc793b0
...
...
@@ -72,7 +72,7 @@ $('#fileupload').fileupload({
add
:
function
(
e
,
data
)
{
submitBtn
.
unbind
(
'click'
);
var
file
=
data
.
files
[
0
];
if
(
file
.
type
==
"application/x-gzip"
)
{
if
(
file
.
name
.
match
(
/tar
\.
gz$/
)
)
{
submitBtn
.
click
(
function
(
e
){
e
.
preventDefault
();
submitBtn
.
hide
();
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -13,6 +13,8 @@
data-webm-source=
"xmodule/include/fixtures/test.webm"
data-ogg-source=
"xmodule/include/fixtures/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"
>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
View file @
3cc793b0
...
...
@@ -13,6 +13,8 @@
data-webm-source=
"xmodule/include/fixtures/test.webm"
data-ogg-source=
"xmodule/include/fixtures/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"
>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
View file @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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
:
'xmodule/include/fixtures/test.mp4'
,
webm
:
'xmodule/include/fixtures/test.webm'
,
ogg
:
'xmodule/include/fixtures/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 @
3cc793b0
...
...
@@ -147,8 +147,6 @@ function (VideoPlayer) {
if
(
state
.
parseYoutubeStreams
(
state
.
config
.
youtubeStreams
))
{
state
.
videoType
=
'youtube'
;
state
.
fetchMetadata
();
state
.
parseSpeed
();
return
true
;
}
return
false
;
...
...
@@ -157,9 +155,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
,
...
...
@@ -168,20 +164,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
)
{
...
...
@@ -205,7 +220,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
;
...
...
@@ -231,28 +246,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
);
...
...
@@ -298,7 +346,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
,
...
...
@@ -308,7 +362,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
;
}
}
});
}
...
...
@@ -325,7 +386,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
;
}
});
});
}
...
...
@@ -362,12 +425,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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -164,6 +164,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
(),
...
...
@@ -178,7 +184,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 @
3cc793b0
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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
#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 @
3cc793b0
lms/djangoapps/courseware/mock_youtube_server/mock_youtube_server.py
0 → 100644
View file @
3cc793b0
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 @
3cc793b0
"""
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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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 @
3cc793b0
...
...
@@ -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/templates/video.html
View file @
3cc793b0
...
...
@@ -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