Commit b292f6c2 by Anton Stupak

Merge pull request #2792 from edx/anton/fix-cc-button

Anton/fix cc button
parents 78fe797e 65adb7b2
...@@ -75,32 +75,8 @@ ...@@ -75,32 +75,8 @@
expect(state.el).toBe('#video_id'); expect(state.el).toBe('#video_id');
}); });
it('parse the videos if subtitles exist', function () { it('doesn\'t have `videos` dictionary', function () {
var sub = 'Z5KLxerq05Y'; expect(state.videos).toBeUndefined();
expect(state.videos).toEqual({
'0.75': sub,
'1.0': sub,
'1.25': sub,
'1.50': sub
});
});
it(
'parse the videos if subtitles do not exist',
function ()
{
var sub = '';
$('#example').find('.video').data('sub', '');
state = new window.Video('#example');
expect(state.videos).toEqual({
'0.75': sub,
'1.0': sub,
'1.25': sub,
'1.50': sub
});
}); });
it('parse Html5 sources', function () { it('parse Html5 sources', function () {
......
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice') window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
.andReturn(null); .andReturn(null);
state = jasmine.initializePlayer();
videoControl = state.videoControl;
$.fn.scrollTo.reset(); $.fn.scrollTo.reset();
}); });
...@@ -29,18 +27,20 @@ ...@@ -29,18 +27,20 @@
describe('always', function () { describe('always', function () {
beforeEach(function () { beforeEach(function () {
spyOn($, 'ajaxWithPrefix').andCallThrough(); spyOn($, 'ajaxWithPrefix').andCallThrough();
state = jasmine.initializePlayer();
}); });
it('create the caption element', function () { it('create the caption element', function () {
state = jasmine.initializePlayer();
expect($('.video')).toContain('ol.subtitles'); expect($('.video')).toContain('ol.subtitles');
}); });
it('add caption control to video player', function () { it('add caption control to video player', function () {
state = jasmine.initializePlayer();
expect($('.video')).toContain('a.hide-subtitles'); expect($('.video')).toContain('a.hide-subtitles');
}); });
it('add ARIA attributes to caption control', function () { it('add ARIA attributes to caption control', function () {
state = jasmine.initializePlayer();
var captionControl = $('a.hide-subtitles'); var captionControl = $('a.hide-subtitles');
expect(captionControl).toHaveAttrs({ expect(captionControl).toHaveAttrs({
'role': 'button', 'role': 'button',
...@@ -49,7 +49,11 @@ ...@@ -49,7 +49,11 @@
}); });
}); });
it('fetch the caption', function () { it('fetch the caption in HTML5 mode', function () {
runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () { waitsFor(function () {
if (state.videoCaption.loaded === true) { if (state.videoCaption.loaded === true) {
return true; return true;
...@@ -62,29 +66,62 @@ ...@@ -62,29 +66,62 @@
expect($.ajaxWithPrefix).toHaveBeenCalledWith({ expect($.ajaxWithPrefix).toHaveBeenCalledWith({
url: '/transcript/translation', url: '/transcript/translation',
notifyOnError: false, notifyOnError: false,
data: { data: jasmine.any(Object),
videoId: 'Z5KLxerq05Y', success: jasmine.any(Function),
error: jasmine.any(Function)
});
expect($.ajaxWithPrefix.mostRecentCall.args[0].data)
.toEqual({
language: 'en' language: 'en'
}, });
});
});
it('fetch the caption in Youtube mode', function () {
runs(function () {
state = jasmine.initializePlayerYouTube();
});
waitsFor(function () {
if (state.videoCaption.loaded === true) {
return true;
}
return false;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT);
runs(function () {
expect($.ajaxWithPrefix).toHaveBeenCalledWith({
url: '/transcript/translation',
notifyOnError: false,
data: jasmine.any(Object),
success: jasmine.any(Function), success: jasmine.any(Function),
error: jasmine.any(Function) error: jasmine.any(Function)
}); });
expect($.ajaxWithPrefix.mostRecentCall.args[0].data)
.toEqual({
language: 'en',
videoId: 'abcdefghijkl'
});
}); });
}); });
it('bind window resize event', function () { it('bind window resize event', function () {
state = jasmine.initializePlayer();
expect($(window)).toHandleWith( expect($(window)).toHandleWith(
'resize', state.videoCaption.resize 'resize', state.videoCaption.resize
); );
}); });
it('bind the hide caption button', function () { it('bind the hide caption button', function () {
state = jasmine.initializePlayer();
expect($('.hide-subtitles')).toHandleWith( expect($('.hide-subtitles')).toHandleWith(
'click', state.videoCaption.toggle 'click', state.videoCaption.toggle
); );
}); });
it('bind the mouse movement', function () { it('bind the mouse movement', function () {
state = jasmine.initializePlayer();
expect($('.subtitles')).toHandleWith( expect($('.subtitles')).toHandleWith(
'mouseover', state.videoCaption.onMouseEnter 'mouseover', state.videoCaption.onMouseEnter
); );
...@@ -103,6 +140,7 @@ ...@@ -103,6 +140,7 @@
}); });
it('bind the scroll', function () { it('bind the scroll', function () {
state = jasmine.initializePlayer();
expect($('.subtitles')) expect($('.subtitles'))
.toHandleWith('scroll', state.videoControl.showControls); .toHandleWith('scroll', state.videoControl.showControls);
}); });
...@@ -284,7 +322,8 @@ ...@@ -284,7 +322,8 @@
describe('when no captions file was specified', function () { describe('when no captions file was specified', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer('video_all.html', { state = jasmine.initializePlayer('video_all.html', {
'sub': '' 'sub': '',
'transcriptLanguages': {},
}); });
}); });
...@@ -395,6 +434,8 @@ ...@@ -395,6 +434,8 @@
}); });
it('reRenderCaption', function () { it('reRenderCaption', function () {
state = jasmine.initializePlayer();
var Caption = state.videoCaption, var Caption = state.videoCaption,
li; li;
...@@ -426,14 +467,6 @@ ...@@ -426,14 +467,6 @@
spyOn(state, 'youtubeId').andReturn('Z5KLxerq05Y'); spyOn(state, 'youtubeId').andReturn('Z5KLxerq05Y');
}); });
it('do not fetch captions, if 1.0 speed is absent', function () {
state.youtubeId.andReturn(void(0));
Caption.fetchCaption();
expect($.ajaxWithPrefix).not.toHaveBeenCalled();
expect(Caption.hideCaptions).not.toHaveBeenCalled();
});
it('show caption on language change', function () { it('show caption on language change', function () {
Caption.loaded = true; Caption.loaded = true;
Caption.fetchCaption(); Caption.fetchCaption();
......
...@@ -45,7 +45,6 @@ function (VideoPlayer) { ...@@ -45,7 +45,6 @@ function (VideoPlayer) {
it('create video caption', function () { it('create video caption', function () {
expect(state.videoCaption).toBeDefined(); expect(state.videoCaption).toBeDefined();
expect(state.youtubeId('1.0')).toEqual('Z5KLxerq05Y');
expect(state.speed).toEqual('1.50'); expect(state.speed).toEqual('1.50');
expect(state.config.transcriptTranslationUrl) expect(state.config.transcriptTranslationUrl)
.toEqual('/transcript/translation'); .toEqual('/transcript/translation');
......
...@@ -202,12 +202,6 @@ function (VideoPlayer, VideoStorage) { ...@@ -202,12 +202,6 @@ function (VideoPlayer, VideoStorage) {
); );
state.speeds = ['0.75', '1.0', '1.25', '1.50']; 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.50': state.config.sub
};
// We must have at least one non-YouTube video source available. // We must have at least one non-YouTube video source available.
// Otherwise, return a negative. // Otherwise, return a negative.
......
...@@ -461,7 +461,7 @@ function (HTML5Video, Resizer) { ...@@ -461,7 +461,7 @@ function (HTML5Video, Resizer) {
this.videoPlayer.log( this.videoPlayer.log(
'pause_video', 'pause_video',
{ {
'currentTime': this.videoPlayer.currentTime currentTime: this.videoPlayer.currentTime
} }
); );
...@@ -482,7 +482,7 @@ function (HTML5Video, Resizer) { ...@@ -482,7 +482,7 @@ function (HTML5Video, Resizer) {
this.videoPlayer.log( this.videoPlayer.log(
'play_video', 'play_video',
{ {
'currentTime': this.videoPlayer.currentTime currentTime: this.videoPlayer.currentTime
} }
); );
...@@ -863,8 +863,7 @@ function (HTML5Video, Resizer) { ...@@ -863,8 +863,7 @@ function (HTML5Video, Resizer) {
// Default parameters that always get logged. // Default parameters that always get logged.
logInfo = { logInfo = {
'id': this.id, id: this.id
'code': this.youtubeId()
}; };
// If extra parameters were passed to the log. // If extra parameters were passed to the log.
......
...@@ -226,14 +226,10 @@ function () { ...@@ -226,14 +226,10 @@ function () {
*/ */
function fetchCaption() { function fetchCaption() {
var self = this, var self = this,
Caption = self.videoCaption; Caption = self.videoCaption,
// Check whether the captions file was specified. This is the point data = {
// where we either stop with the caption panel (so that a white empty language: this.getCurrentLanguage()
// panel to the right of the video will not be shown), or carry on };
// further.
if (!this.youtubeId('1.0')) {
return false;
}
if (Caption.loaded) { if (Caption.loaded) {
Caption.hideCaptions(false); Caption.hideCaptions(false);
...@@ -245,15 +241,16 @@ function () { ...@@ -245,15 +241,16 @@ function () {
Caption.fetchXHR.abort(); Caption.fetchXHR.abort();
} }
if (this.videoType === 'youtube') {
data.videoId = this.youtubeId();
}
// Fetch the captions file. If no file was specified, or if an error // Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button // occurred, then we hide the captions panel, and the "CC" button
Caption.fetchXHR = $.ajaxWithPrefix({ Caption.fetchXHR = $.ajaxWithPrefix({
url: self.config.transcriptTranslationUrl, url: self.config.transcriptTranslationUrl,
notifyOnError: false, notifyOnError: false,
data: { data: data,
videoId: this.youtubeId(),
language: this.getCurrentLanguage()
},
success: function (captions) { success: function (captions) {
Caption.captions = captions.text; Caption.captions = captions.text;
Caption.start = captions.start; Caption.start = captions.start;
......
...@@ -10,7 +10,6 @@ in-browser HTML5 video method (when in HTML5 mode). ...@@ -10,7 +10,6 @@ in-browser HTML5 video method (when in HTML5 mode).
in XML. in XML.
""" """
import os
import json import json
import logging import logging
from operator import itemgetter from operator import itemgetter
...@@ -329,7 +328,7 @@ class VideoModule(VideoFields, XModule): ...@@ -329,7 +328,7 @@ class VideoModule(VideoFields, XModule):
`available_translations`: returns list of languages, for which SRT files exist. For 'en' check if SJSON exists. `available_translations`: returns list of languages, for which SRT files exist. For 'en' check if SJSON exists.
""" """
if dispatch == 'translation': if dispatch == 'translation':
if 'language' not in request.GET or 'videoId' not in request.GET: if 'language' not in request.GET:
log.info("Invalid /transcript GET parameters.") log.info("Invalid /transcript GET parameters.")
return Response(status=400) return Response(status=400)
...@@ -341,7 +340,7 @@ class VideoModule(VideoFields, XModule): ...@@ -341,7 +340,7 @@ class VideoModule(VideoFields, XModule):
self.transcript_language = lang self.transcript_language = lang
try: try:
transcript = self.translation(request.GET.get('videoId')) transcript = self.translation(request.GET.get('videoId', None))
except (TranscriptException, NotFoundError) as ex: except (TranscriptException, NotFoundError) as ex:
log.info(ex.message) log.info(ex.message)
response = Response(status=404) response = Response(status=404)
...@@ -390,26 +389,30 @@ class VideoModule(VideoFields, XModule): ...@@ -390,26 +389,30 @@ class VideoModule(VideoFields, XModule):
return response return response
def translation(self, subs_id): def translation(self, youtube_id):
""" """
This is called to get transcript file for specific language. This is called to get transcript file for specific language.
subs_id: str: must be on of: self.sub or one of youtube_ids. youtube_id: str: must be one of youtube_ids or None if HTML video
Logic flow: Logic flow:
If english -> give back `sub` subtitles: If youtube_id doesn't exist, we have a video in HTML5 mode. Otherwise,
Return what we have in contentstore for given subs_id, video video in Youtube or Flash modes.
We should not regenerate needed transcripts, if, for example, they present for youtube 1.0 speed,
and we need for other speeds. Such generation should be done in transcripts workflow. if youtube:
If english -> give back youtube_id subtitles:
Return what we have in contentstore for given youtube_id.
If non-english: If non-english:
a) extract subs_id from srt file name a) extract youtube_id from srt file name.
b) try to find sjson by youtube_id and return if successful.
c) generate sjson from srt for all youtube speeds.
if non-youtube: if non-youtube:
b) try to find sjson by subs_id and return if sucessful If english -> give back `sub` subtitles:
c) otherwise generate sjson from srt and return it. Return what we have in contentstore for given subs_if that is stored in self.sub.
if youtube: If non-english:
b) try to find sjson by subs_id and return if sucessful a) try to find previously generated sjson.
c) generate sjson from srt for all youtube speeds b) otherwise generate sjson from srt and return it.
Filenames naming: Filenames naming:
en: subs_videoid.srt.sjson en: subs_videoid.srt.sjson
...@@ -418,28 +421,36 @@ class VideoModule(VideoFields, XModule): ...@@ -418,28 +421,36 @@ class VideoModule(VideoFields, XModule):
Raises: Raises:
NotFoundError if for 'en' subtitles no asset is uploaded. NotFoundError if for 'en' subtitles no asset is uploaded.
""" """
if self.transcript_language == 'en':
return asset(self.location, subs_id).data
if not self.youtube_id_1_0: # Non-youtube (HTML5) case:
return get_or_create_sjson(self)
if youtube_id:
# Youtube case: # Youtube case:
if self.transcript_language == 'en':
return asset(self.location, youtube_id).data
youtube_ids = youtube_speed_dict(self) youtube_ids = youtube_speed_dict(self)
assert subs_id in youtube_ids assert youtube_id in youtube_ids
try: try:
sjson_transcript = asset(self.location, subs_id, self.transcript_language).data sjson_transcript = asset(self.location, youtube_id, self.transcript_language).data
except (NotFoundError): except (NotFoundError):
log.info("Can't find content in storage for %s transcript: generating.", subs_id) log.info("Can't find content in storage for %s transcript: generating.", youtube_id)
generate_sjson_for_all_speeds( generate_sjson_for_all_speeds(
self, self,
self.transcripts[self.transcript_language], self.transcripts[self.transcript_language],
{speed: subs_id for subs_id, speed in youtube_ids.iteritems()}, {speed: youtube_id for youtube_id, speed in youtube_ids.iteritems()},
self.transcript_language self.transcript_language
) )
sjson_transcript = asset(self.location, subs_id, self.transcript_language).data sjson_transcript = asset(self.location, youtube_id, self.transcript_language).data
return sjson_transcript return sjson_transcript
else:
# HTML5 case
if self.transcript_language == 'en':
return asset(self.location, self.sub).data
else:
return get_or_create_sjson(self)
class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor): class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor):
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Feature: LMS Video component Feature: LMS Video component
As a student, I want to view course videos in LMS As a student, I want to view course videos in LMS
# 0 # 1
Scenario: Video component stores position correctly when page is reloaded Scenario: Video component stores position correctly when page is reloaded
Given the course has a Video component 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 Then when I view the video it has rendered in Youtube mode
...@@ -13,51 +13,51 @@ Feature: LMS Video component ...@@ -13,51 +13,51 @@ Feature: LMS Video component
And I click video button "play" And I click video button "play"
Then I see video starts playing from "0:10" position Then I see video starts playing from "0:10" position
# 1 # 2
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
Then when I view the video it has rendered in HTML5 mode Then when I view the video it has rendered in HTML5 mode
And all sources are correct And all sources are correct
# 2 # 3
# Firefox doesn't have HTML5 (only mp4 - fix here) # Firefox doesn't have HTML5 (only mp4 - fix here)
@skip_firefox @skip_firefox
Scenario: Autoplay is disabled in LMS for a Video component Scenario: Autoplay is disabled in LMS for a Video component
Given the course has a Video component in HTML5 mode Given the course has a Video component in HTML5 mode
Then when I view the video it does not have autoplay enabled Then when I view the video it does not have autoplay enabled
# 3 # 4
# Youtube testing # Youtube testing
Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources 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 Given youtube server is up and response time is 0.4 seconds
And the course has a Video component in Youtube_HTML5 mode And the course has a Video component in Youtube_HTML5 mode
Then when I view the video it has rendered in Youtube mode Then when I view the video it has rendered in Youtube mode
# 4 # 5
Scenario: Video component is not rendered in the LMS in Youtube mode with HTML5 sources 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5 mode And the course has a Video component in Youtube_HTML5 mode
Then when I view the video it has rendered in HTML5 mode Then when I view the video it has rendered in HTML5 mode
# 5 # 6
Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube mode And the course has a Video component in Youtube mode
Then when I view the video it has rendered in Youtube mode Then when I view the video it has rendered in Youtube mode
# 6 # 7
Scenario: Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5_Unsupported_Video mode 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 Then when I view the video it has rendered in Youtube mode
# 7 # 8
Scenario: Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser 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 Given the course has a Video component in HTML5_Unsupported_Video mode
Then error message is shown Then error message is shown
And error message has correct text And error message has correct text
# 8 # 9
Scenario: Video component stores speed correctly when each video is in separate sequence Scenario: Video component stores speed correctly when each video is in separate sequence
Given I am registered for the course "test_course" Given I am registered for the course "test_course"
And it has a video "A" in "Youtube" mode in position "1" of sequential And it has a video "A" in "Youtube" mode in position "1" of sequential
...@@ -79,8 +79,8 @@ Feature: LMS Video component ...@@ -79,8 +79,8 @@ Feature: LMS Video component
When I open video "C" When I open video "C"
Then video "C" should start playing at speed "1.0" Then video "C" should start playing at speed "1.0"
# 9 # 10
Scenario: Language menu in Video component works correctly Scenario: Language menu works correctly in Video component
Given the course has a Video component in Youtube mode: Given the course has a Video component in Youtube mode:
| transcripts | sub | | transcripts | sub |
| {"zh": "OEoXaMPEzfM"} | OEoXaMPEzfM | | {"zh": "OEoXaMPEzfM"} | OEoXaMPEzfM |
...@@ -90,3 +90,42 @@ Feature: LMS Video component ...@@ -90,3 +90,42 @@ Feature: LMS Video component
Then I see "好 各位同学" text in the captions Then I see "好 各位同学" text in the captions
And I select language with code "en" And I select language with code "en"
And I see "Hi, welcome to Edx." text in the captions And I see "Hi, welcome to Edx." text in the captions
# 11
Scenario: CC button works correctly w/o english transcript in HTML5 mode of Video component
Given the course has a Video component in HTML5 mode:
| transcripts |
| {"zh": "OEoXaMPEzfM"} |
And I make sure captions are opened
Then I see "好 各位同学" text in the captions
# 12
Scenario: CC button works correctly only w/ english transcript in HTML5 mode of Video component
Given I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "HTML5" mode:
| sub |
| OEoXaMPEzfM |
And I make sure captions are opened
Then I see "Hi, welcome to Edx." text in the captions
# 13
Scenario: CC button works correctly w/o english transcript in Youtube mode of Video component
Given the course has a Video component in Youtube mode:
| transcripts |
| {"zh": "OEoXaMPEzfM"} |
And I make sure captions are opened
Then I see "好 各位同学" text in the captions
# 14
Scenario: CC button works correctly if transcripts and sub fields are empty, but transcript file exists is assets (Youtube mode of Video component)
Given I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode
And I make sure captions are opened
Then I see "Hi, welcome to Edx." text in the captions
# 15
Scenario: CC button is hidden if no translations
Given the course has a Video component in Youtube mode
Then button "CC" is hidden
...@@ -9,7 +9,6 @@ from django.conf import settings ...@@ -9,7 +9,6 @@ from django.conf import settings
from cache_toolbox.core import del_cached_content from cache_toolbox.core import del_cached_content
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
import os import os
from functools import partial
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
LANGUAGES = settings.ALL_LANGUAGES LANGUAGES = settings.ALL_LANGUAGES
...@@ -46,48 +45,6 @@ VIDEO_BUTTONS = { ...@@ -46,48 +45,6 @@ VIDEO_BUTTONS = {
coursenum = 'test_course' coursenum = 'test_course'
sequence = {} sequence = {}
@step('when I view the (.*) it does not have autoplay enabled$')
def does_not_autoplay(_step, video_type):
assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False')
@step('the course has a Video component in (.*) mode(?:\:)?$')
def view_video(_step, player_mode):
i_am_registered_for_the_course(_step, coursenum)
# Make sure we have a video
add_video_to_course(coursenum, player_mode.lower(), _step.hashes)
visit_scenario_item('SECTION')
@step('a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$')
def add_video(_step, player_id, player_mode, position):
sequence[player_id] = position
add_video_to_course(coursenum, player_mode.lower(), _step.hashes, display_name=player_id)
@step('I open the section with videos$')
def visit_video_section(_step):
visit_scenario_item('SECTION')
@step('I select the "([^"]*)" speed on video "([^"]*)"$')
def change_video_speed(_step, speed, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id])
_change_video_speed(speed)
@step('I open video "([^"]*)"$')
def open_video(_step, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id])
@step('video "([^"]*)" should start playing at speed "([^"]*)"$')
def check_video_speed(_step, player_id, speed):
speed_css = '.speeds p.active'
assert world.css_has_text(speed_css, '{0}x'.format(speed))
def add_video_to_course(course, player_mode, hashes, display_name='Video'): def add_video_to_course(course, player_mode, hashes, display_name='Video'):
category = 'video' category = 'video'
...@@ -129,16 +86,105 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'): ...@@ -129,16 +86,105 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
if 'transcripts' in kwargs['metadata']: if 'transcripts' in kwargs['metadata']:
kwargs['metadata']['transcripts'] = json.loads(kwargs['metadata']['transcripts']) kwargs['metadata']['transcripts'] = json.loads(kwargs['metadata']['transcripts'])
course_location = world.scenario_dict['COURSE'].location
if 'sub' in kwargs['metadata']: if 'sub' in kwargs['metadata']:
_upload_file(kwargs['metadata']['sub'], 'en', world.scenario_dict['COURSE'].location) filename = _get_transcript_filename(kwargs['metadata']['sub'], 'en')
_upload_file(filename, course_location)
for lang, videoId in kwargs['metadata']['transcripts'].items(): for lang, videoId in kwargs['metadata']['transcripts'].items():
_upload_file(videoId, lang, world.scenario_dict['COURSE'].location) filename = _get_transcript_filename(videoId, lang)
_upload_file(filename, course_location)
world.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs) world.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs)
def _get_transcript_filename(videoId, lang):
if lang == 'en':
return 'subs_{0}.srt.sjson'.format(videoId)
else:
return '{0}_subs_{1}.srt.sjson'.format(lang, videoId)
def _upload_file(filename, location):
path = os.path.join(TEST_ROOT, 'uploads/', filename)
f = open(os.path.abspath(path))
mime_type = "application/json"
content_location = StaticContent.compute_location(
location.org, location.course, filename
)
content = StaticContent(content_location, filename, mime_type, f.read())
contentstore().save(content)
del_cached_content(content.location)
def _navigate_to_an_item_in_a_sequence(number):
sequence_css = 'a[data-element="{0}"]'.format(number)
world.css_click(sequence_css)
def _change_video_speed(speed):
world.browser.execute_script("$('.speeds').addClass('open')")
speed_css = 'li[data-speed="{0}"] a'.format(speed)
world.css_click(speed_css)
def _open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('open')".format(
selector=VIDEO_MENUS[menu]
))
@step('when I view the (.*) it does not have autoplay enabled$')
def does_not_autoplay(_step, video_type):
assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False')
@step('the course has a Video component in (.*) mode(?:\:)?$')
def view_video(_step, player_mode):
i_am_registered_for_the_course(_step, coursenum)
# Make sure we have a video
add_video_to_course(coursenum, player_mode.lower(), _step.hashes)
visit_scenario_item('SECTION')
@step('a video in "([^"]*)" mode(?:\:)?$')
def add_video(_step, player_mode):
add_video_to_course(coursenum, player_mode.lower(), _step.hashes)
visit_scenario_item('SECTION')
@step('a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$')
def add_video_in_position(_step, player_id, player_mode, position):
sequence[player_id] = position
add_video_to_course(coursenum, player_mode.lower(), _step.hashes, display_name=player_id)
@step('I open the section with videos$')
def visit_video_section(_step):
visit_scenario_item('SECTION')
@step('I select the "([^"]*)" speed on video "([^"]*)"$')
def change_video_speed(_step, speed, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id])
_change_video_speed(speed)
@step('I open video "([^"]*)"$')
def open_video(_step, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id])
@step('video "([^"]*)" should start playing at speed "([^"]*)"$')
def check_video_speed(_step, player_id, speed):
speed_css = '.speeds p.active'
assert world.css_has_text(speed_css, '{0}x'.format(speed))
@step('youtube server is up and response time is (.*) seconds$') @step('youtube server is up and response time is (.*) seconds$')
def set_youtube_response_timeout(_step, time): def set_youtube_response_timeout(_step, time):
world.youtube.config['time_to_response'] = float(time) world.youtube.config['time_to_response'] = float(time)
...@@ -232,47 +278,6 @@ def click_button(_step, button): ...@@ -232,47 +278,6 @@ def click_button(_step, button):
world.css_find(VIDEO_BUTTONS[button]).click() world.css_find(VIDEO_BUTTONS[button]).click()
def _upload_file(videoId, lang, location):
if lang == 'en':
filename = 'subs_{0}.srt.sjson'.format(videoId)
else:
filename = '{0}_subs_{1}.srt.sjson'.format(lang, videoId)
path = os.path.join(TEST_ROOT, 'uploads/', filename)
f = open(os.path.abspath(path))
mime_type = "application/json"
content_location = StaticContent.compute_location(
location.org, location.course, filename
)
sc_partial = partial(StaticContent, content_location, filename, mime_type)
content = sc_partial(f.read())
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(
content,
tempfile_path=None
)
del_cached_content(thumbnail_location)
if thumbnail_content is not None:
content.thumbnail_location = thumbnail_location
contentstore().save(content)
del_cached_content(content.location)
def _navigate_to_an_item_in_a_sequence(number):
sequence_css = 'a[data-element="{0}"]'.format(number)
world.css_click(sequence_css)
def _change_video_speed(speed):
world.browser.execute_script("$('.speeds').addClass('open')")
speed_css = 'li[data-speed="{0}"] a'.format(speed)
world.css_click(speed_css)
@step('I click video button "([^"]*)"$') @step('I click video button "([^"]*)"$')
def click_button_video(_step, button_type): def click_button_video(_step, button_type):
world.wait_for_ajax_complete() world.wait_for_ajax_complete()
...@@ -295,7 +300,12 @@ def seek_video_to_n_seconds(_step, seconds): ...@@ -295,7 +300,12 @@ def seek_video_to_n_seconds(_step, seconds):
world.browser.execute_script(jsCode) world.browser.execute_script(jsCode)
def _open_menu(menu): @step('I have a "([^"]*)" transcript file in assets$')
world.browser.execute_script("$('{selector}').parent().addClass('open')".format( def upload_to_assets(_step, filename):
selector=VIDEO_MENUS[menu] _upload_file(filename, world.scenario_dict['COURSE'].location)
))
@step('button "([^"]*)" is hidden$')
def is_hidden_button(_step, button):
assert not world.css_visible(VIDEO_BUTTONS[button])
...@@ -189,17 +189,22 @@ class TestVideoTranscriptTranslation(TestVideo): ...@@ -189,17 +189,22 @@ class TestVideoTranscriptTranslation(TestVideo):
# Tests for `translation` dispatch: # Tests for `translation` dispatch:
def test_translation_fails(self): def test_translation_fails(self):
# No videoId # No language
request = Request.blank('/translation?language=ru') request = Request.blank('/translation')
response = self.item.transcript(request=request, dispatch='translation') response = self.item.transcript(request=request, dispatch='translation')
self.assertEqual(response.status, '400 Bad Request') self.assertEqual(response.status, '400 Bad Request')
# No videoId - HTML5 video with language that is not in available languages
request = Request.blank('/translation?language=ru')
response = self.item.transcript(request=request, dispatch='translation')
self.assertEqual(response.status, '404 Not Found')
# Language is not in available languages # Language is not in available languages
request = Request.blank('/translation?language=ru&videoId=12345') request = Request.blank('/translation?language=ru&videoId=12345')
response = self.item.transcript(request=request, dispatch='translation') response = self.item.transcript(request=request, dispatch='translation')
self.assertEqual(response.status, '404 Not Found') self.assertEqual(response.status, '404 Not Found')
def test_translaton_en_success(self): def test_translaton_en_youtube_success(self):
subs = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]} subs = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]}
good_sjson = _create_file(json.dumps(subs)) good_sjson = _create_file(json.dumps(subs))
_upload_sjson_file(good_sjson, self.item_descriptor.location) _upload_sjson_file(good_sjson, self.item_descriptor.location)
...@@ -210,25 +215,7 @@ class TestVideoTranscriptTranslation(TestVideo): ...@@ -210,25 +215,7 @@ class TestVideoTranscriptTranslation(TestVideo):
response = self.item.transcript(request=request, dispatch='translation') response = self.item.transcript(request=request, dispatch='translation')
self.assertDictEqual(json.loads(response.body), subs) self.assertDictEqual(json.loads(response.body), subs)
def test_translaton_non_en_non_youtube_success(self): def test_translation_non_en_youtube_success(self):
subs = {
u'end': [100],
u'start': [12],
u'text': [
u'\u041f\u0440\u0438\u0432\u0456\u0442, edX \u0432\u0456\u0442\u0430\u0454 \u0432\u0430\u0441.'
]
}
self.non_en_file.seek(0)
_upload_file(self.non_en_file, self.item_descriptor.location, os.path.split(self.non_en_file.name)[1])
subs_id = _get_subs_id(self.non_en_file.name)
# manually clean youtube_id_1_0, as it has default value
self.item.youtube_id_1_0 = ""
request = Request.blank('/translation?language=uk&videoId={}'.format(subs_id))
response = self.item.transcript(request=request, dispatch='translation')
self.assertDictEqual(json.loads(response.body), subs)
def test_translation_non_en_youtube(self):
subs = { subs = {
u'end': [100], u'end': [100],
u'start': [12], u'start': [12],
...@@ -270,6 +257,34 @@ class TestVideoTranscriptTranslation(TestVideo): ...@@ -270,6 +257,34 @@ class TestVideoTranscriptTranslation(TestVideo):
} }
self.assertDictEqual(json.loads(response.body), calculated_1_5) self.assertDictEqual(json.loads(response.body), calculated_1_5)
def test_translaton_en_html5_success(self):
subs = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]}
good_sjson = _create_file(json.dumps(subs))
_upload_sjson_file(good_sjson, self.item_descriptor.location)
subs_id = _get_subs_id(good_sjson.name)
self.item.sub = subs_id
request = Request.blank('/translation?language=en')
response = self.item.transcript(request=request, dispatch='translation')
self.assertDictEqual(json.loads(response.body), subs)
def test_translaton_non_en_html5_success(self):
subs = {
u'end': [100],
u'start': [12],
u'text': [
u'\u041f\u0440\u0438\u0432\u0456\u0442, edX \u0432\u0456\u0442\u0430\u0454 \u0432\u0430\u0441.'
]
}
self.non_en_file.seek(0)
_upload_file(self.non_en_file, self.item_descriptor.location, os.path.split(self.non_en_file.name)[1])
# manually clean youtube_id_1_0, as it has default value
self.item.youtube_id_1_0 = ""
request = Request.blank('/translation?language=uk')
response = self.item.transcript(request=request, dispatch='translation')
self.assertDictEqual(json.loads(response.body), subs)
class TestVideoTranscriptsDownload(TestVideo): class TestVideoTranscriptsDownload(TestVideo):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment