Commit ddf41ce9 by polesye

BLD-915: Fix issues with different videoIDs.

parent e117957e
...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
Blades: Fix issues related to videos that have separate YouTube IDs for the
different video speeds. BLD-915, BLD-901.
Blades: Add .txt and .srt options to the "download transcript" button. BLD-844. Blades: Add .txt and .srt options to the "download transcript" button. BLD-844.
Blades: Fix bug when transcript cutting off view in full view mode. BLD-852. Blades: Fix bug when transcript cutting off view in full view mode. BLD-852.
......
...@@ -15,8 +15,8 @@ SERVICES = { ...@@ -15,8 +15,8 @@ SERVICES = {
} }
@before.all @before.each_scenario
def start_stubs(): def start_stubs(_):
""" """
Start each stub service running on a local port. Start each stub service running on a local port.
""" """
...@@ -25,7 +25,7 @@ def start_stubs(): ...@@ -25,7 +25,7 @@ def start_stubs():
setattr(world, name, fake_server) setattr(world, name, fake_server)
@after.all @after.each_scenario
def stop_stubs(_): def stop_stubs(_):
""" """
Shut down each stub service. Shut down each stub service.
......
...@@ -5,7 +5,6 @@ Stub implementation of YouTube for acceptance tests. ...@@ -5,7 +5,6 @@ Stub implementation of YouTube for acceptance tests.
from .http import StubHttpRequestHandler, StubHttpService from .http import StubHttpRequestHandler, StubHttpService
import json import json
import time import time
import requests
from urlparse import urlparse from urlparse import urlparse
from collections import OrderedDict from collections import OrderedDict
...@@ -80,7 +79,7 @@ class StubYouTubeHandler(StubHttpRequestHandler): ...@@ -80,7 +79,7 @@ class StubYouTubeHandler(StubHttpRequestHandler):
'data': OrderedDict({ 'data': OrderedDict({
'id': youtube_id, 'id': youtube_id,
'message': message, 'message': message,
'duration': 60, 'duration': 60 if youtube_id == 'OEoXaMPEzfM' else 77,
}) })
}) })
response = "{cb}({data})".format(cb=callback, data=json.dumps(data)) response = "{cb}({data})".format(cb=callback, data=json.dumps(data))
......
...@@ -233,25 +233,42 @@ function (Initialize) { ...@@ -233,25 +233,42 @@ function (Initialize) {
'1.0': 'testId', '1.0': 'testId',
'1.50': 'videoId' '1.50': 'videoId'
}, },
youtubeId: Initialize.prototype.youtubeId youtubeId: Initialize.prototype.youtubeId,
isFlashMode: jasmine.createSpy().andReturn(false)
}; };
}); });
it('returns duration for current video', function () { var msg = 'returns duration for the 1.0 speed if speed is not 1.0';
var expected = Initialize.prototype.getDuration.call(state);
expect(expected).toEqual(100);
});
var msg = 'returns duration for the 1.0 speed as a fallback';
it(msg, function () { it(msg, function () {
var expected; var expected;
state.speed = '2.0'; state.speed = '1.50';
expected = Initialize.prototype.getDuration.call(state); expected = Initialize.prototype.getDuration.call(state);
expect(expected).toEqual(400); expect(expected).toEqual(400);
}); });
describe('Flash mode', function () {
it('returns duration for current video', function () {
var expected;
state.isFlashMode.andReturn(true);
expected = Initialize.prototype.getDuration.call(state);
expect(expected).toEqual(100);
});
var msg = 'returns duration for the 1.0 speed as a fall-back';
it(msg, function () {
var expected;
state.isFlashMode.andReturn(true);
state.speed = '2.0';
expected = Initialize.prototype.getDuration.call(state);
expect(expected).toEqual(400);
});
});
}); });
describe('youtubeId', function () { describe('youtubeId', function () {
...@@ -262,7 +279,8 @@ function (Initialize) { ...@@ -262,7 +279,8 @@ function (Initialize) {
'0.50': '7tqY6eQzVhE', '0.50': '7tqY6eQzVhE',
'1.0': 'cogebirgzzM', '1.0': 'cogebirgzzM',
'1.50': 'abcdefghijkl' '1.50': 'abcdefghijkl'
} },
isFlashMode: jasmine.createSpy().andReturn(false)
}; };
}); });
...@@ -278,14 +296,25 @@ function (Initialize) { ...@@ -278,14 +296,25 @@ function (Initialize) {
}); });
}); });
describe('without speed', function () { describe('without speed for flash mode', function () {
it('return the video id for current speed', function () { it('return the video id for current speed', function () {
var expected = Initialize.prototype.youtubeId.call(state); var expected;
state.isFlashMode.andReturn(true);
expected = Initialize.prototype.youtubeId.call(state);
expect(expected).toEqual('abcdefghijkl'); expect(expected).toEqual('abcdefghijkl');
}); });
}); });
describe('without speed for youtube html5 mode', function () {
it('return the video id for 1.0x speed', function () {
var expected = Initialize.prototype.youtubeId.call(state);
expect(expected).toEqual('cogebirgzzM');
});
});
describe('speed is absent in the list of video speeds', function () { describe('speed is absent in the list of video speeds', function () {
it('return the video id for 1.0x speed', function () { it('return the video id for 1.0x speed', function () {
var expected = Initialize.prototype.youtubeId.call(state, '0.0'); var expected = Initialize.prototype.youtubeId.call(state, '0.0');
......
...@@ -55,11 +55,7 @@ ...@@ -55,11 +55,7 @@
}); });
waitsFor(function () { waitsFor(function () {
if (state.videoCaption.loaded === true) { return state.videoCaption.loaded;
return true;
}
return false;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT); }, 'Expect captions to be loaded.', WAIT_TIMEOUT);
runs(function () { runs(function () {
...@@ -77,17 +73,15 @@ ...@@ -77,17 +73,15 @@
}); });
}); });
it('fetch the caption in Youtube mode', function () { it('fetch the caption in Flash mode', function () {
runs(function () { runs(function () {
state = jasmine.initializePlayerYouTube(); state = jasmine.initializePlayerYouTube();
spyOn(state, 'isFlashMode').andReturn(true);
state.videoCaption.fetchCaption();
}); });
waitsFor(function () { waitsFor(function () {
if (state.videoCaption.loaded === true) { return state.videoCaption.loaded;
return true;
}
return false;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT); }, 'Expect captions to be loaded.', WAIT_TIMEOUT);
runs(function () { runs(function () {
...@@ -106,6 +100,31 @@ ...@@ -106,6 +100,31 @@
}); });
}); });
it('fetch the caption in Youtube mode', function () {
runs(function () {
state = jasmine.initializePlayerYouTube();
});
waitsFor(function () {
return state.videoCaption.loaded;
}, '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),
error: jasmine.any(Function)
});
expect($.ajaxWithPrefix.mostRecentCall.args[0].data)
.toEqual({
language: 'en',
videoId: 'cogebirgzzM'
});
});
});
it('bind the hide caption button', function () { it('bind the hide caption button', function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
expect($('.hide-subtitles')).toHandleWith( expect($('.hide-subtitles')).toHandleWith(
......
...@@ -105,6 +105,7 @@ function (VideoPlayer) { ...@@ -105,6 +105,7 @@ function (VideoPlayer) {
it('create Flash player', function () { it('create Flash player', function () {
var player; var player;
spyOn($.fn, 'trigger');
state = jasmine.initializePlayerYouTube(); state = jasmine.initializePlayerYouTube();
state.videoEl = state.el.find('video, iframe').width(100); state.videoEl = state.el.find('video, iframe').width(100);
player = state.videoPlayer.player; player = state.videoPlayer.player;
...@@ -715,7 +716,8 @@ function (VideoPlayer) { ...@@ -715,7 +716,8 @@ function (VideoPlayer) {
}, },
currentPlayerMode: 'html5', currentPlayerMode: 'html5',
trigger: function () {}, trigger: function () {},
browserIsFirefox: false browserIsFirefox: false,
isFlashMode: jasmine.createSpy().andReturn(false)
}; };
}); });
...@@ -1015,7 +1017,8 @@ function (VideoPlayer) { ...@@ -1015,7 +1017,8 @@ function (VideoPlayer) {
updatePlayTime: jasmine.createSpy(), updatePlayTime: jasmine.createSpy(),
setPlaybackRate: jasmine.createSpy(), setPlaybackRate: jasmine.createSpy(),
player: jasmine.createSpyObj('player', ['setPlaybackRate']) player: jasmine.createSpyObj('player', ['setPlaybackRate'])
} },
isFlashMode: jasmine.createSpy().andReturn(false)
}; };
}); });
...@@ -1033,7 +1036,7 @@ function (VideoPlayer) { ...@@ -1033,7 +1036,7 @@ function (VideoPlayer) {
}); });
it('convert the current time to the new speed', function () { it('convert the current time to the new speed', function () {
state.currentPlayerMode = 'flash'; state.isFlashMode.andReturn(true);
VideoPlayer.prototype.onSpeedChange.call(state, '0.75', false); VideoPlayer.prototype.onSpeedChange.call(state, '0.75', false);
expect(state.videoPlayer.currentTime).toBe('120.000'); expect(state.videoPlayer.currentTime).toBe('120.000');
}); });
......
...@@ -65,6 +65,7 @@ function (VideoPlayer, VideoStorage) { ...@@ -65,6 +65,7 @@ function (VideoPlayer, VideoStorage) {
getDuration: getDuration, getDuration: getDuration,
getVideoMetadata: getVideoMetadata, getVideoMetadata: getVideoMetadata,
initialize: initialize, initialize: initialize,
isFlashMode: isFlashMode,
parseSpeed: parseSpeed, parseSpeed: parseSpeed,
parseVideoSources: parseVideoSources, parseVideoSources: parseVideoSources,
parseYoutubeStreams: parseYoutubeStreams, parseYoutubeStreams: parseYoutubeStreams,
...@@ -550,7 +551,7 @@ function (VideoPlayer, VideoStorage) { ...@@ -550,7 +551,7 @@ function (VideoPlayer, VideoStorage) {
_this.videos[speed] = video[1]; _this.videos[speed] = video[1];
}); });
return true; return _.isString(this.videos['1.0']);
} }
// function parseVideoSources(, mp4Source, webmSource, oggSource) // function parseVideoSources(, mp4Source, webmSource, oggSource)
...@@ -702,7 +703,11 @@ function (VideoPlayer, VideoStorage) { ...@@ -702,7 +703,11 @@ function (VideoPlayer, VideoStorage) {
} }
function youtubeId(speed) { function youtubeId(speed) {
return this.videos[speed || this.speed] || this.videos['1.0']; var currentSpeed = this.isFlashMode() ? this.speed : '1.0';
return this.videos[speed] ||
this.videos[currentSpeed] ||
this.videos['1.0'];
} }
function getDuration() { function getDuration() {
...@@ -713,6 +718,10 @@ function (VideoPlayer, VideoStorage) { ...@@ -713,6 +718,10 @@ function (VideoPlayer, VideoStorage) {
} }
} }
function isFlashMode() {
return this.currentPlayerMode === 'flash';
}
function getCurrentLanguage() { function getCurrentLanguage() {
var keys = _.keys(this.config.transcriptLanguages); var keys = _.keys(this.config.transcriptLanguages);
......
...@@ -73,7 +73,7 @@ function (HTML5Video, Resizer) { ...@@ -73,7 +73,7 @@ function (HTML5Video, Resizer) {
state.videoPlayer.ready = _.once(function () { state.videoPlayer.ready = _.once(function () {
$(window).on('unload', state.saveState); $(window).on('unload', state.saveState);
if (state.currentPlayerMode !== 'flash') { if (!state.isFlashMode()) {
state.videoPlayer.setPlaybackRate(state.speed); state.videoPlayer.setPlaybackRate(state.speed);
} }
state.videoPlayer.player.setVolume(state.currentVolume); state.videoPlayer.player.setVolume(state.currentVolume);
...@@ -100,7 +100,7 @@ function (HTML5Video, Resizer) { ...@@ -100,7 +100,7 @@ function (HTML5Video, Resizer) {
modestbranding: 1 modestbranding: 1
}; };
if (state.currentPlayerMode !== 'flash') { if (!state.isFlashMode()) {
state.videoPlayer.playerVars.html5 = 1; state.videoPlayer.playerVars.html5 = 1;
} }
...@@ -140,11 +140,8 @@ function (HTML5Video, Resizer) { ...@@ -140,11 +140,8 @@ function (HTML5Video, Resizer) {
}, false); }, false);
} else { // if (state.videoType === 'youtube') { } else { // if (state.videoType === 'youtube') {
if (state.currentPlayerMode === 'flash') { youTubeId = state.youtubeId();
youTubeId = state.youtubeId();
} else {
youTubeId = state.youtubeId('1.0');
}
state.videoPlayer.player = new YT.Player(state.id, { state.videoPlayer.player = new YT.Player(state.id, {
playerVars: state.videoPlayer.playerVars, playerVars: state.videoPlayer.playerVars,
videoId: youTubeId, videoId: youTubeId,
...@@ -162,22 +159,7 @@ function (HTML5Video, Resizer) { ...@@ -162,22 +159,7 @@ function (HTML5Video, Resizer) {
videoHeight = player.attr('height') || player.height(); videoHeight = player.attr('height') || player.height();
_resize(state, videoWidth, videoHeight); _resize(state, videoWidth, videoHeight);
_updateVcrAndRegion(state, true);
// After initialization, update the VCR with total time.
// At this point only the metadata duration is available (not
// very precise), but it is better than having 00:00:00 for
// total time.
if (state.youtubeMetadataReceived) {
// Metadata was already received, and is available.
_updateVcrAndRegion(state);
} else {
// We wait for metadata to arrive, before we request the update
// of the VCR video time, and of the start-end time region.
// Metadata contains duration of the video.
state.el.on('metadata_received', function () {
_updateVcrAndRegion(state);
});
}
}); });
} }
...@@ -185,36 +167,53 @@ function (HTML5Video, Resizer) { ...@@ -185,36 +167,53 @@ function (HTML5Video, Resizer) {
dfd.resolve(); dfd.resolve();
} }
} }
function _updateVcrAndRegion(state, isYoutube) {
var update = function (state) {
var duration = state.videoPlayer.duration(),
time;
function _updateVcrAndRegion(state) { time = state.videoPlayer.figureOutStartingTime(duration);
var duration = state.videoPlayer.duration(),
time;
time = state.videoPlayer.figureOutStartingTime(duration); // Update the VCR.
state.trigger(
'videoControl.updateVcrVidTime',
{
time: time,
duration: duration
}
);
// Update the VCR. // Update the time slider.
state.trigger( state.trigger(
'videoControl.updateVcrVidTime', 'videoProgressSlider.updateStartEndTimeRegion',
{ {
time: time, duration: duration
duration: duration }
} );
); state.trigger(
'videoProgressSlider.updatePlayTime',
{
time: time,
duration: duration
}
);
};
// Update the time slider. // After initialization, update the VCR with total time.
state.trigger( // At this point only the metadata duration is available (not
'videoProgressSlider.updateStartEndTimeRegion', // very precise), but it is better than having 00:00:00 for
{ // total time.
duration: duration if (state.youtubeMetadataReceived || !isYoutube) {
} // Metadata was already received, and is available.
); update(state);
state.trigger( } else {
'videoProgressSlider.updatePlayTime', // We wait for metadata to arrive, before we request the update
{ // of the VCR video time, and of the start-end time region.
time: time, // Metadata contains duration of the video.
duration: duration state.el.on('metadata_received', function () {
} update(state);
); });
}
} }
function _resize(state, videoWidth, videoHeight) { function _resize(state, videoWidth, videoHeight) {
...@@ -271,6 +270,8 @@ function (HTML5Video, Resizer) { ...@@ -271,6 +270,8 @@ function (HTML5Video, Resizer) {
} }
}); });
_updateVcrAndRegion(state, true);
state.trigger('videoCaption.fetchCaption', null);
state.resizer.setElement(state.el.find('iframe')).align(); state.resizer.setElement(state.el.find('iframe')).align();
} }
...@@ -348,7 +349,7 @@ function (HTML5Video, Resizer) { ...@@ -348,7 +349,7 @@ function (HTML5Video, Resizer) {
// where in Firefox speed switching to 1.0 in HTML5 player mode is // where in Firefox speed switching to 1.0 in HTML5 player mode is
// handled incorrectly by YouTube API. // handled incorrectly by YouTube API.
methodName = 'cueVideoById'; methodName = 'cueVideoById';
youtubeId = this.youtubeId(); youtubeId = this.youtubeId(newSpeed);
if (this.videoPlayer.isPlaying()) { if (this.videoPlayer.isPlaying()) {
methodName = 'loadVideoById'; methodName = 'loadVideoById';
...@@ -360,10 +361,9 @@ function (HTML5Video, Resizer) { ...@@ -360,10 +361,9 @@ function (HTML5Video, Resizer) {
} }
function onSpeedChange(newSpeed) { function onSpeedChange(newSpeed) {
var time = this.videoPlayer.currentTime, var time = this.videoPlayer.currentTime;
isFlash = this.currentPlayerMode === 'flash';
if (isFlash) { if (this.isFlashMode()) {
this.videoPlayer.currentTime = Time.convert( this.videoPlayer.currentTime = Time.convert(
time, time,
parseFloat(this.speed), parseFloat(this.speed),
...@@ -605,6 +605,11 @@ function (HTML5Video, Resizer) { ...@@ -605,6 +605,11 @@ function (HTML5Video, Resizer) {
} }
} }
if (this.isFlashMode()) {
this.setSpeed(this.speed);
this.trigger('videoSpeedControl.setSpeed', this.speed);
}
if (this.currentPlayerMode === 'html5') { if (this.currentPlayerMode === 'html5') {
this.videoPlayer.player.setPlaybackRate(this.speed); this.videoPlayer.player.setPlaybackRate(this.speed);
} }
...@@ -653,7 +658,7 @@ function (HTML5Video, Resizer) { ...@@ -653,7 +658,7 @@ function (HTML5Video, Resizer) {
videoPlayer.startTime = this.config.startTime; videoPlayer.startTime = this.config.startTime;
if (videoPlayer.startTime >= duration) { if (videoPlayer.startTime >= duration) {
videoPlayer.startTime = 0; videoPlayer.startTime = 0;
} else if (this.currentPlayerMode === 'flash') { } else if (this.isFlashMode()) {
videoPlayer.startTime /= Number(this.speed); videoPlayer.startTime /= Number(this.speed);
} }
...@@ -664,7 +669,7 @@ function (HTML5Video, Resizer) { ...@@ -664,7 +669,7 @@ function (HTML5Video, Resizer) {
) { ) {
videoPlayer.stopAtEndTime = false; videoPlayer.stopAtEndTime = false;
videoPlayer.endTime = null; videoPlayer.endTime = null;
} else if (this.currentPlayerMode === 'flash') { } else if (this.isFlashMode()) {
videoPlayer.endTime /= Number(this.speed); videoPlayer.endTime /= Number(this.speed);
} }
} }
...@@ -749,11 +754,7 @@ function (HTML5Video, Resizer) { ...@@ -749,11 +754,7 @@ function (HTML5Video, Resizer) {
// HTML5 video sources play fine from start-time in both Chrome // HTML5 video sources play fine from start-time in both Chrome
// and Firefox. // and Firefox.
if (this.browserIsFirefox && this.videoType === 'youtube') { if (this.browserIsFirefox && this.videoType === 'youtube') {
if (this.currentPlayerMode === 'flash') { youTubeId = this.youtubeId();
youTubeId = this.youtubeId();
} else {
youTubeId = this.youtubeId('1.0');
}
// When we will call cueVideoById() for some strange reason // When we will call cueVideoById() for some strange reason
// an ENDED event will be fired. It really does no damage // an ENDED event will be fired. It really does no damage
......
...@@ -104,8 +104,7 @@ function () { ...@@ -104,8 +104,7 @@ function () {
// whole slider). Remember that endTime === null means the end-time // whole slider). Remember that endTime === null means the end-time
// is set to the end of video by default. // is set to the end of video by default.
function updateStartEndTimeRegion(params) { function updateStartEndTimeRegion(params) {
var isFlashMode = this.currentPlayerMode === 'flash', var left, width, start, end, duration, rangeParams;
left, width, start, end, duration, rangeParams;
// We must have a duration in order to determine the area of range. // We must have a duration in order to determine the area of range.
// It also must be non-zero. // It also must be non-zero.
...@@ -120,7 +119,7 @@ function () { ...@@ -120,7 +119,7 @@ function () {
if (start > duration) { if (start > duration) {
start = 0; start = 0;
} else if (isFlashMode) { } else if (this.isFlashMode()) {
start /= Number(this.speed); start /= Number(this.speed);
} }
...@@ -128,7 +127,7 @@ function () { ...@@ -128,7 +127,7 @@ function () {
// video, then we set it to the end of the video. // video, then we set it to the end of the video.
if (end === null || end > duration) { if (end === null || end > duration) {
end = duration; end = duration;
} else if (isFlashMode) { } else if (this.isFlashMode()) {
end /= Number(this.speed); end /= Number(this.speed);
} }
......
...@@ -155,7 +155,7 @@ function () { ...@@ -155,7 +155,7 @@ function () {
} }
this.el.on('speedchange', function () { this.el.on('speedchange', function () {
if (self.currentPlayerMode === 'flash') { if (self.isFlashMode()) {
Caption.fetchCaption(); Caption.fetchCaption();
} }
}); });
...@@ -628,7 +628,7 @@ function () { ...@@ -628,7 +628,7 @@ function () {
if (this.videoCaption.loaded) { if (this.videoCaption.loaded) {
// Current mode === 'flash' can only be for YouTube videos. So, we // Current mode === 'flash' can only be for YouTube videos. So, we
// don't have to also check for videoType === 'youtube'. // don't have to also check for videoType === 'youtube'.
if (this.currentPlayerMode === 'flash') { if (this.isFlashMode()) {
// Total play time changes with speed change. Also there is // Total play time changes with speed change. Also there is
// a 250 ms delay we have to take into account. // a 250 ms delay we have to take into account.
time = Math.round( time = Math.round(
...@@ -670,7 +670,7 @@ function () { ...@@ -670,7 +670,7 @@ function () {
// Current mode === 'flash' can only be for YouTube videos. So, we // Current mode === 'flash' can only be for YouTube videos. So, we
// don't have to also check for videoType === 'youtube'. // don't have to also check for videoType === 'youtube'.
if (this.currentPlayerMode === 'flash') { if (this.isFlashMode()) {
// Total play time changes with speed change. Also there is // Total play time changes with speed change. Also there is
// a 250 ms delay we have to take into account. // a 250 ms delay we have to take into account.
time = Math.round( time = Math.round(
......
{
"start": [
1000
],
"end": [
2000
],
"text": [
"Equal transcripts"
]
}
\ No newline at end of file
...@@ -62,12 +62,12 @@ Feature: LMS Video component ...@@ -62,12 +62,12 @@ Feature: LMS Video component
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
And a video "B" in "Youtube" mode in position "2" of sequential And a video "B" in "Youtube" mode in position "2" of sequential
And a video "C" in "Youtube" mode in position "3" of sequential And a video "C" in "HTML5" mode in position "3" of sequential
And I open the section with videos And I open the section with videos
And I select the "2.0" speed on video "A" And I select the "2.0" speed on video "A"
And I select the "0.50" speed on video "B" And I select the "0.50" speed on video "B"
When I open video "C" When I open video "C"
Then video "C" should start playing at speed "0.50" Then video "C" should start playing at speed "0.75"
When I open video "A" When I open video "A"
Then video "A" should start playing at speed "2.0" Then video "A" should start playing at speed "2.0"
And I reload the page And I reload the page
...@@ -94,7 +94,7 @@ Feature: LMS Video component ...@@ -94,7 +94,7 @@ Feature: LMS Video component
# 11 # 11
Scenario: CC button works correctly w/o english transcript in HTML5 mode of Video component 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: Given the course has a Video component in HTML5 mode:
| transcripts | | transcripts |
| {"zh": "chinese_transcripts.srt"} | | {"zh": "chinese_transcripts.srt"} |
And I make sure captions are opened And I make sure captions are opened
Then I see "好 各位同学" text in the captions Then I see "好 各位同学" text in the captions
...@@ -112,13 +112,13 @@ Feature: LMS Video component ...@@ -112,13 +112,13 @@ Feature: LMS Video component
# 13 # 13
Scenario: CC button works correctly w/o english transcript in Youtube mode of Video component 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: Given the course has a Video component in Youtube mode:
| transcripts | | transcripts |
| {"zh": "chinese_transcripts.srt"} | | {"zh": "chinese_transcripts.srt"} |
And I make sure captions are opened And I make sure captions are opened
Then I see "好 各位同学" text in the captions Then I see "好 各位同学" text in the captions
# 14 # 14
Scenario: CC button works correctly if transcripts and sub fields are empty, but transcript file exists is assets (Youtube mode of Video component) Scenario: CC button works correctly if transcripts and sub fields are empty, but transcript file exists in assets (Youtube mode of Video component)
Given I am registered for the course "test_course" Given I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode And it has a video in "Youtube" mode
...@@ -176,3 +176,15 @@ Feature: LMS Video component ...@@ -176,3 +176,15 @@ Feature: LMS Video component
Then I can download transcript in "txt" format Then I can download transcript in "txt" format
When I open video "C" When I open video "C"
Then menu "download_transcript" doesn't exist Then menu "download_transcript" doesn't exist
# 20
Scenario: Youtube video has correct transcript if fields for other speeds are filled.
Given the course has a Video component in Youtube mode:
| sub | youtube_id_1_5 |
| OEoXaMPEzfM | b7xgknqkQk8 |
And I make sure captions are opened
Then I see "Hi, welcome to Edx." text in the captions
And I select the "1.50" speed
And I reload the page
Then I see "Hi, welcome to Edx." text in the captions
And I see duration "1:00"
...@@ -5,6 +5,7 @@ from lettuce import world, step ...@@ -5,6 +5,7 @@ from lettuce import world, step
import json import json
import os import os
import requests import requests
import time
from common import i_am_registered_for_the_course, section_location, visit_scenario_item from common import i_am_registered_for_the_course, section_location, visit_scenario_item
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
...@@ -94,6 +95,21 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'): ...@@ -94,6 +95,21 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
'metadata': {}, 'metadata': {},
} }
course_location =world.scenario_dict['COURSE'].location
if hashes:
kwargs['metadata'].update(hashes[0])
conversions = {
'transcripts': json.loads,
'download_track': json.loads,
'download_video': json.loads,
}
for key in kwargs['metadata']:
if key in conversions:
kwargs['metadata'][key] = conversions[key](kwargs['metadata'][key])
if player_mode == 'html5': if player_mode == 'html5':
kwargs['metadata'].update({ kwargs['metadata'].update({
'youtube_id_1_0': '', 'youtube_id_1_0': '',
...@@ -119,28 +135,18 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'): ...@@ -119,28 +135,18 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
'html5_sources': HTML5_SOURCES_INCORRECT 'html5_sources': HTML5_SOURCES_INCORRECT
}) })
if hashes: if kwargs['metadata'].get('sub'):
kwargs['metadata'].update(hashes[0])
course_location =world.scenario_dict['COURSE'].location
conversions = {
'transcripts': json.loads,
'download_track': json.loads,
'download_video': json.loads,
}
for key in kwargs['metadata']:
if key in conversions:
kwargs['metadata'][key] = conversions[key](kwargs['metadata'][key])
if 'sub' in kwargs['metadata']:
filename = _get_sjson_filename(kwargs['metadata']['sub'], 'en') filename = _get_sjson_filename(kwargs['metadata']['sub'], 'en')
_upload_file(filename, course_location) _upload_file(filename, course_location)
if 'transcripts' in kwargs['metadata']: if kwargs['metadata'].get('transcripts'):
for lang, filename in kwargs['metadata']['transcripts'].items(): for lang, filename in kwargs['metadata']['transcripts'].items():
_upload_file(filename, course_location) _upload_file(filename, course_location)
if kwargs['metadata'].get('youtube_id_1_5'):
filename = _get_sjson_filename(kwargs['metadata']['youtube_id_1_5'], 'en')
_upload_file(filename, course_location)
world.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs) world.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs)
...@@ -208,6 +214,36 @@ def _set_window_dimensions(width, height): ...@@ -208,6 +214,36 @@ def _set_window_dimensions(width, height):
world.wait(0.2) world.wait(0.2)
def _duration():
"""
Total duration of the video, in seconds.
"""
elapsed_time, duration = _video_time()
return duration
def _video_time():
"""
Return a tuple `(elapsed_time, duration)`, each in seconds.
"""
# The full time has the form "0:32 / 3:14"
full_time = world.css_text('div.vidtime')
# Split the time at the " / ", to get ["0:32", "3:14"]
elapsed_str, duration_str = full_time.split(' / ')
# Convert each string to seconds
return (_parse_time_str(elapsed_str), _parse_time_str(duration_str))
def _parse_time_str(time_str):
"""
Parse a string of the form 1:23 into seconds (int).
"""
time_obj = time.strptime(time_str, '%M:%S')
return time_obj.tm_min * 60 + time_obj.tm_sec
@step('when I view the (.*) it does not have autoplay enabled$') @step('when I view the (.*) it does not have autoplay enabled$')
def does_not_autoplay(_step, video_type): def does_not_autoplay(_step, video_type):
assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False') assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False')
...@@ -237,8 +273,13 @@ def visit_video_section(_step): ...@@ -237,8 +273,13 @@ def visit_video_section(_step):
visit_scenario_item('SECTION') visit_scenario_item('SECTION')
@step('I select the "([^"]*)" speed$')
def change_video_speed(_step, speed):
_change_video_speed(speed)
@step('I select the "([^"]*)" speed on video "([^"]*)"$') @step('I select the "([^"]*)" speed on video "([^"]*)"$')
def change_video_speed(_step, speed, player_id): def change_video_speed_on_video(_step, speed, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id]) _navigate_to_an_item_in_a_sequence(sequence[player_id])
_change_video_speed(speed) _change_video_speed(speed)
...@@ -355,6 +396,14 @@ def start_playing_video_from_n_seconds(_step, position): ...@@ -355,6 +396,14 @@ def start_playing_video_from_n_seconds(_step, position):
) )
@step('I see duration "([^"]*)"$')
def i_see_duration(_step, position):
world.wait_for(
func=lambda _: _duration() == _parse_time_str(position),
timeout=5
)
@step('I seek video to "([^"]*)" seconds$') @step('I seek video to "([^"]*)" seconds$')
def seek_video_to_n_seconds(_step, seconds): def seek_video_to_n_seconds(_step, seconds):
time = float(seconds.strip()) time = float(seconds.strip())
......
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