Merge pull request #2230 from edx/anton/store-video-state
Persist speed preferences between videos.
This commit is contained in:
@@ -5,6 +5,8 @@ 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
|
||||
the top. Include a label indicating the component affected.
|
||||
|
||||
Blades: Video player persist speed preferences between videos. BLD-237.
|
||||
|
||||
Blades: Change the download video field to a dropdown that will allow students
|
||||
to download the first source listed in the alternate sources. BLD-364.
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
<div
|
||||
id="video_id"
|
||||
class="video closed"
|
||||
data-streams="0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
|
||||
data-streams="0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl"
|
||||
data-show-captions="true"
|
||||
data-save-state-url="/save_user_state"
|
||||
data-speed="1.5"
|
||||
data-start=""
|
||||
data-end=""
|
||||
data-caption-asset-path="/static/subs/"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
id="video_id"
|
||||
class="video closed"
|
||||
data-show-captions="true"
|
||||
data-save-state-url="/save_user_state"
|
||||
data-speed="1.5"
|
||||
data-start=""
|
||||
data-end=""
|
||||
data-caption-asset-path="/static/subs/"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
id="video_id"
|
||||
class="video closed"
|
||||
data-show-captions="true"
|
||||
data-save-state-url="/save_user_state"
|
||||
data-speed="1.5"
|
||||
data-start=""
|
||||
data-end=""
|
||||
data-caption-asset-path="/static/subs/"
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
<div
|
||||
id="video_id"
|
||||
class="video closed"
|
||||
data-streams="0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
|
||||
data-streams="0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl"
|
||||
data-show-captions="false"
|
||||
data-save-state-url="/save_user_state"
|
||||
data-speed="1.5"
|
||||
data-start=""
|
||||
data-end=""
|
||||
data-caption-asset-path="/static/subs/"
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
<div
|
||||
id="video_id1"
|
||||
class="video closed"
|
||||
data-streams="0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
|
||||
data-streams="0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl"
|
||||
data-show-captions="true"
|
||||
data-save-state-url="/save_user_state"
|
||||
data-speed="1.5"
|
||||
data-start=""
|
||||
data-end=""
|
||||
data-caption-asset-path="/static/subs/"
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
id: 'cogebirgzzM',
|
||||
duration: 200
|
||||
},
|
||||
'abcdefghijkl': {
|
||||
id: 'abcdefghijkl',
|
||||
duration: 400
|
||||
},
|
||||
bogus: {
|
||||
duration: 100
|
||||
}
|
||||
@@ -189,6 +193,8 @@
|
||||
settings.url.match(/.+\/problem_(check|reset|show|save)$/)
|
||||
) {
|
||||
// Do nothing.
|
||||
} else if (settings.url == '/save_user_state') {
|
||||
return {success: true};
|
||||
} else {
|
||||
throw 'External request attempted for ' +
|
||||
settings.url +
|
||||
|
||||
@@ -32,7 +32,7 @@ function (CookieStorage) {
|
||||
it('unload', function () {
|
||||
var expected = JSON.stringify({
|
||||
storage: {
|
||||
'item_2': {
|
||||
item_2: {
|
||||
value: 'value_2',
|
||||
session: false
|
||||
}
|
||||
@@ -51,7 +51,7 @@ function (CookieStorage) {
|
||||
describe('methods: ', function () {
|
||||
var data = {
|
||||
storage: {
|
||||
'item_1': {
|
||||
item_1: {
|
||||
value: 'value_1',
|
||||
session: false
|
||||
}
|
||||
@@ -69,15 +69,15 @@ function (CookieStorage) {
|
||||
it('pass correct data', function () {
|
||||
var expected = JSON.stringify({
|
||||
storage: {
|
||||
'item_1': {
|
||||
item_1: {
|
||||
value: 'value_1',
|
||||
session: false
|
||||
},
|
||||
'item_2': {
|
||||
item_2: {
|
||||
value: 'value_2',
|
||||
session: false
|
||||
},
|
||||
'item_3': {
|
||||
item_3: {
|
||||
value: 'value_3',
|
||||
session: true
|
||||
},
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
beforeEach(function () {
|
||||
jasmine.stubRequests();
|
||||
this.videosDefinition = '0.75:7tqY6eQzVhE,1.0:cogebirgzzM';
|
||||
this['7tqY6eQzVhE'] = '7tqY6eQzVhE';
|
||||
this['cogebirgzzM'] = 'cogebirgzzM';
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@@ -17,7 +14,7 @@
|
||||
describe('YT', function () {
|
||||
beforeEach(function () {
|
||||
loadFixtures('video.html');
|
||||
$.cookie.andReturn('0.75');
|
||||
$.cookie.andReturn('0.50');
|
||||
});
|
||||
|
||||
describe('by default', function () {
|
||||
@@ -35,17 +32,18 @@
|
||||
|
||||
it('parse the videos', function () {
|
||||
expect(this.state.videos).toEqual({
|
||||
'0.75': this['7tqY6eQzVhE'],
|
||||
'1.0': this['cogebirgzzM']
|
||||
'0.50': '7tqY6eQzVhE',
|
||||
'1.0': 'cogebirgzzM',
|
||||
'1.50': 'abcdefghijkl'
|
||||
});
|
||||
});
|
||||
|
||||
it('parse available video speeds', function () {
|
||||
expect(this.state.speeds).toEqual(['0.75', '1.0']);
|
||||
expect(this.state.speeds).toEqual(['0.50', '1.0', '1.50']);
|
||||
});
|
||||
|
||||
it('set current video speed via cookie', function () {
|
||||
expect(this.state.speed).toEqual('0.75');
|
||||
expect(this.state.speed).toEqual('1.50');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -157,7 +155,7 @@
|
||||
});
|
||||
|
||||
it('set current video speed via cookie', function () {
|
||||
expect(state.speed).toEqual('0.75');
|
||||
expect(state.speed).toEqual('1.50');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,16 +188,18 @@
|
||||
|
||||
describe('with speed', function () {
|
||||
it('return the video id for given speed', function () {
|
||||
expect(state.youtubeId('0.75'))
|
||||
.toEqual(this['7tqY6eQzVhE']);
|
||||
expect(state.youtubeId('0.50'))
|
||||
.toEqual('7tqY6eQzVhE');
|
||||
expect(state.youtubeId('1.0'))
|
||||
.toEqual(this['cogebirgzzM']);
|
||||
.toEqual('cogebirgzzM');
|
||||
expect(state.youtubeId('1.50'))
|
||||
.toEqual('abcdefghijkl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without speed', function () {
|
||||
it('return the video id for current speed', function () {
|
||||
expect(state.youtubeId()).toEqual(this.cogebirgzzM);
|
||||
expect(state.youtubeId()).toEqual('abcdefghijkl');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -314,44 +314,25 @@
|
||||
});
|
||||
|
||||
describe('setSpeed', function () {
|
||||
|
||||
describe('YT', function () {
|
||||
beforeEach(function () {
|
||||
loadFixtures('video.html');
|
||||
state = new Video('#example');
|
||||
});
|
||||
|
||||
describe('when new speed is available', function () {
|
||||
beforeEach(function () {
|
||||
state.setSpeed('0.75', true);
|
||||
});
|
||||
it('check mapping', function () {
|
||||
var map = {
|
||||
'0.75': '0.50',
|
||||
'1.25': '1.50'
|
||||
};
|
||||
|
||||
it('set new speed', function () {
|
||||
expect(state.speed).toEqual('0.75');
|
||||
});
|
||||
|
||||
it('save setting for new speed', function () {
|
||||
expect($.cookie).toHaveBeenCalledWith(
|
||||
'video_speed',
|
||||
'0.75',
|
||||
{
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when new speed is not available', function () {
|
||||
beforeEach(function () {
|
||||
state.setSpeed('1.75');
|
||||
});
|
||||
|
||||
it('set speed to 1.0x', function () {
|
||||
expect(state.speed).toEqual('1.0');
|
||||
$.each(map, function(key, expected) {
|
||||
state.setSpeed(key, true);
|
||||
expect(state.speed).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTML5', function () {
|
||||
beforeEach(function () {
|
||||
loadFixtures('video_html5.html');
|
||||
@@ -368,14 +349,9 @@
|
||||
});
|
||||
|
||||
it('save setting for new speed', function () {
|
||||
expect($.cookie).toHaveBeenCalledWith(
|
||||
'video_speed',
|
||||
'0.75',
|
||||
{
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
}
|
||||
);
|
||||
|
||||
expect(state.storage.getItem('general_speed')).toBe('0.75');
|
||||
expect(state.storage.getItem('video_speed_' + state.id)).toBe('0.75');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -388,6 +364,19 @@
|
||||
expect(state.speed).toEqual('1.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('check mapping', function () {
|
||||
var map = {
|
||||
'0.25': '0.75',
|
||||
'0.50': '0.75',
|
||||
'2.0': '1.50'
|
||||
};
|
||||
|
||||
$.each(map, function(key, expected) {
|
||||
state.setSpeed(key, true);
|
||||
expect(state.speed).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -398,7 +387,7 @@
|
||||
});
|
||||
|
||||
it('return duration for current video', function () {
|
||||
expect(state.getDuration()).toEqual(200);
|
||||
expect(state.getDuration()).toEqual(400);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
|
||||
it('create video caption', function () {
|
||||
expect(state.videoCaption).toBeDefined();
|
||||
expect(state.youtubeId()).toEqual('Z5KLxerq05Y');
|
||||
expect(state.speed).toEqual('1.0');
|
||||
expect(state.config.caption_asset_path)
|
||||
expect(state.youtubeId('1.0')).toEqual('Z5KLxerq05Y');
|
||||
expect(state.speed).toEqual('1.50');
|
||||
expect(state.config.captionAssetPath)
|
||||
.toEqual('/static/subs/');
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
expect(state.videoSpeedControl.el).toHaveClass('speeds');
|
||||
expect(state.videoSpeedControl.speeds)
|
||||
.toEqual([ '0.75', '1.0', '1.25', '1.50' ]);
|
||||
expect(state.speed).toEqual('1.0');
|
||||
expect(state.speed).toEqual('1.50');
|
||||
});
|
||||
|
||||
it('create video progress slider', function () {
|
||||
@@ -395,7 +395,7 @@
|
||||
'speed_change_video',
|
||||
{
|
||||
current_time: state.videoPlayer.currentTime,
|
||||
old_speed: '1.0',
|
||||
old_speed: '1.50',
|
||||
new_speed: '0.75'
|
||||
}
|
||||
);
|
||||
@@ -406,7 +406,7 @@
|
||||
});
|
||||
|
||||
it('set video speed to the new speed', function () {
|
||||
expect(state.setSpeed).toHaveBeenCalledWith('0.75', false);
|
||||
expect(state.setSpeed).toHaveBeenCalledWith('0.75', true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
expect(secondaryControls).toContain('.speeds');
|
||||
expect(secondaryControls).toContain('.video_speeds');
|
||||
expect(secondaryControls.find('p.active').text())
|
||||
.toBe('1.0x');
|
||||
.toBe('1.50x');
|
||||
expect(li.filter('.active')).toHaveData(
|
||||
'speed', state.videoSpeedControl.currentSpeed
|
||||
);
|
||||
|
||||
@@ -15,8 +15,6 @@ function() {
|
||||
* @param {string} namespace Namespace that is used to store data.
|
||||
* @return {object} CookieStorage API.
|
||||
*/
|
||||
|
||||
|
||||
var CookieStorage = function (namespace) {
|
||||
var Storage;
|
||||
|
||||
@@ -73,7 +71,7 @@ function() {
|
||||
});
|
||||
|
||||
$.cookie(namespace, JSON.stringify(Storage), {
|
||||
expires: -1,
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
define(
|
||||
'video/01_initialize.js',
|
||||
['video/03_video_player.js'],
|
||||
function (VideoPlayer) {
|
||||
['video/03_video_player.js', 'video/00_cookie_storage.js'],
|
||||
function (VideoPlayer, CookieStorage) {
|
||||
// window.console.log() is expected to be available. We do not support
|
||||
// browsers which lack this functionality.
|
||||
|
||||
@@ -88,7 +88,6 @@ function (VideoPlayer) {
|
||||
function _makeFunctionsPublic(state) {
|
||||
var methodsDict = {
|
||||
bindTo: bindTo,
|
||||
checkStartEndTimes: checkStartEndTimes,
|
||||
fetchMetadata: fetchMetadata,
|
||||
getDuration: getDuration,
|
||||
getVideoMetadata: getVideoMetadata,
|
||||
@@ -141,7 +140,7 @@ function (VideoPlayer) {
|
||||
// Configure displaying of captions.
|
||||
//
|
||||
// Option
|
||||
// this.config.show_captions = true | false
|
||||
// this.config.showCaptions = true | false
|
||||
//
|
||||
// Defines whether or not captions are shown on first viewing.
|
||||
//
|
||||
@@ -151,7 +150,7 @@ function (VideoPlayer) {
|
||||
// represents the user's choice of having the subtitles shown or
|
||||
// hidden. This choice is stored in cookies.
|
||||
function _configureCaptions(state) {
|
||||
if (state.config.show_captions) {
|
||||
if (state.config.showCaptions) {
|
||||
state.hide_captions = ($.cookie('hide_captions') === 'true');
|
||||
} else {
|
||||
state.hide_captions = true;
|
||||
@@ -185,7 +184,7 @@ function (VideoPlayer) {
|
||||
// true: Parsing of YouTube video IDs went OK, and we can proceed
|
||||
// onwards to play YouTube videos.
|
||||
function _parseYouTubeIDs(state) {
|
||||
if (state.parseYoutubeStreams(state.config.youtubeStreams)) {
|
||||
if (state.parseYoutubeStreams(state.config.streams)) {
|
||||
state.videoType = 'youtube';
|
||||
|
||||
return true;
|
||||
@@ -241,10 +240,9 @@ function (VideoPlayer) {
|
||||
|
||||
if (!state.config.sub || !state.config.sub.length) {
|
||||
state.config.sub = '';
|
||||
state.config.show_captions = false;
|
||||
state.config.showCaptions = false;
|
||||
}
|
||||
|
||||
state.setSpeed($.cookie('video_speed'));
|
||||
state.setSpeed(state.speed);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -286,6 +284,79 @@ function (VideoPlayer) {
|
||||
return dfd.promise();
|
||||
}
|
||||
|
||||
function _getConfiguration(data) {
|
||||
var isBoolean = function (value) {
|
||||
var regExp = /^true$/i;
|
||||
return regExp.test(value.toString());
|
||||
},
|
||||
// List of keys that will be extracted form the configuration.
|
||||
extractKeys = ['speed'],
|
||||
// Compatibility keys used to change names of some parameters in
|
||||
// the final configuration.
|
||||
compatKeys = {
|
||||
'start': 'startTime',
|
||||
'end': 'endTime'
|
||||
},
|
||||
// Conversions used to pre-process some configuration data.
|
||||
conversions = {
|
||||
'showCaptions': isBoolean,
|
||||
'autoplay': isBoolean,
|
||||
'autohideHtml5': isBoolean,
|
||||
'ytTestTimeout': function (value) {
|
||||
value = parseInt(value, 10);
|
||||
|
||||
if (!isFinite(value)) {
|
||||
value = 1500;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
'startTime': function (value) {
|
||||
value = parseInt(value, 10);
|
||||
|
||||
if (!isFinite(value) || value < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
'endTime': function (value) {
|
||||
value = parseInt(value, 10);
|
||||
|
||||
if (!isFinite(value) || value === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
},
|
||||
config = {};
|
||||
|
||||
$.each(data, function(option, value) {
|
||||
// Extract option that is in `extractKeys`.
|
||||
if ($.inArray(option, extractKeys) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Change option name to key that is in `compatKeys`.
|
||||
if (compatKeys[option]) {
|
||||
option = compatKeys[option];
|
||||
}
|
||||
|
||||
// Pre-process data.
|
||||
if (conversions[option]) {
|
||||
if ($.isFunction(conversions[option])) {
|
||||
value = conversions[option].call(this, value);
|
||||
} else {
|
||||
throw new TypeError(option + ' is not a function.');
|
||||
}
|
||||
}
|
||||
config[option] = value;
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// ***************************************************************
|
||||
// Public functions start here.
|
||||
// These are available via the 'state' object. Their context ('this'
|
||||
@@ -316,75 +387,60 @@ function (VideoPlayer) {
|
||||
// The function set initial configuration and preparation.
|
||||
|
||||
function initialize(element) {
|
||||
var _this = this,
|
||||
regExp = /^true$/i,
|
||||
data, tempYtTestTimeout;
|
||||
// This is used in places where we instead would have to check if an
|
||||
// element has a CSS class 'fullscreen'.
|
||||
this.__dfd__ = $.Deferred();
|
||||
this.isFullScreen = false;
|
||||
this.currentVolume = 100;
|
||||
this.isTouch = onTouchBasedDevice() || '';
|
||||
var self = this,
|
||||
el = $(element).find('.video'),
|
||||
container = el.find('.video-wrapper'),
|
||||
id = el.attr('id').replace(/video_/, ''),
|
||||
__dfd__ = $.Deferred(),
|
||||
isTouch = onTouchBasedDevice() || '',
|
||||
storage = CookieStorage('video_player'),
|
||||
speed = storage.getItem('video_speed_' + id) ||
|
||||
storage.getItem('general_speed') ||
|
||||
el.data('speed').toFixed(2).replace(/\.00$/, '.0') || '1.0';
|
||||
|
||||
// The parent element of the video, and the ID.
|
||||
this.el = $(element).find('.video');
|
||||
this.elVideoWrapper = this.el.find('.video-wrapper');
|
||||
this.id = this.el.attr('id').replace(/video_/, '');
|
||||
|
||||
if (this.isTouch) {
|
||||
this.el.addClass('is-touch');
|
||||
if (isTouch) {
|
||||
el.addClass('is-touch');
|
||||
}
|
||||
|
||||
// jQuery .data() return object with keys in lower camelCase format.
|
||||
data = this.el.data();
|
||||
$.extend(this, {
|
||||
__dfd__: __dfd__,
|
||||
el: el,
|
||||
container: container,
|
||||
currentVolume: 100,
|
||||
id: id,
|
||||
isFullScreen: false,
|
||||
isTouch: isTouch,
|
||||
speed: speed,
|
||||
storage: storage
|
||||
});
|
||||
|
||||
console.log(
|
||||
'[Video info]: Initializing video with id "' + this.id + '".'
|
||||
'[Video info]: Initializing video with id "' + id + '".'
|
||||
);
|
||||
|
||||
// We store all settings passed to us by the server in one place. These
|
||||
// are "read only", so don't modify them. All variable content lives in
|
||||
// 'state' object.
|
||||
this.config = {
|
||||
// jQuery .data() return object with keys in lower camelCase format.
|
||||
this.config = $.extend({}, _getConfiguration(el.data()), {
|
||||
element: element,
|
||||
|
||||
startTime: data['start'],
|
||||
endTime: data['end'],
|
||||
caption_data_dir: data['captionDataDir'],
|
||||
caption_asset_path: data['captionAssetPath'],
|
||||
show_captions: regExp.test(data['showCaptions'].toString()),
|
||||
youtubeStreams: data['streams'],
|
||||
autohideHtml5: regExp.test(data['autohideHtml5'].toString()),
|
||||
sub: data['sub'],
|
||||
mp4Source: data['mp4Source'],
|
||||
webmSource: data['webmSource'],
|
||||
oggSource: data['oggSource'],
|
||||
ytTestUrl: data['ytTestUrl'],
|
||||
fadeOutTimeout: 1400,
|
||||
captionsFreezeTime: 10000,
|
||||
availableQualities: ['hd720', 'hd1080', 'highres']
|
||||
};
|
||||
});
|
||||
|
||||
// Make sure that start end end times are valid. If not, they will be
|
||||
// set to `null` and will not be used later on.
|
||||
this.checkStartEndTimes();
|
||||
|
||||
// Check if the YT test timeout has been set. If not, or it is in
|
||||
// improper format, then set to default value.
|
||||
tempYtTestTimeout = parseInt(data['ytTestTimeout'], 10);
|
||||
if (!isFinite(tempYtTestTimeout)) {
|
||||
tempYtTestTimeout = 1500;
|
||||
if (this.config.endTime < this.config.startTime) {
|
||||
this.config.endTime = null;
|
||||
}
|
||||
this.config.ytTestTimeout = tempYtTestTimeout;
|
||||
|
||||
if (!(_parseYouTubeIDs(this))) {
|
||||
|
||||
// If we do not have YouTube ID's, try parsing HTML5 video sources.
|
||||
if (!_prepareHTML5Video(this)) {
|
||||
|
||||
this.__dfd__.reject();
|
||||
__dfd__.reject();
|
||||
// Non-YouTube sources were not found either.
|
||||
return this.__dfd__.promise();
|
||||
return __dfd__.promise();
|
||||
}
|
||||
|
||||
console.log('[Video info]: Start player in HTML5 mode.');
|
||||
@@ -406,13 +462,13 @@ function (VideoPlayer) {
|
||||
if (err) {
|
||||
console.log(
|
||||
'[Video info]: YouTube returned an error for ' +
|
||||
'video with id "' + _this.id + '".'
|
||||
'video with id "' + id + '".'
|
||||
);
|
||||
|
||||
// When the youtube link doesn't work for any reason
|
||||
// (for example, the great firewall in china) any
|
||||
// alternate sources should automatically play.
|
||||
if (!_prepareHTML5Video(_this)) {
|
||||
if (!_prepareHTML5Video(self)) {
|
||||
console.log(
|
||||
'[Video info]: Continue loading ' +
|
||||
'YouTube video.'
|
||||
@@ -420,15 +476,15 @@ function (VideoPlayer) {
|
||||
|
||||
// Non-YouTube sources were not found either.
|
||||
|
||||
_this.el.find('.video-player div')
|
||||
el.find('.video-player div')
|
||||
.removeClass('hidden');
|
||||
_this.el.find('.video-player h3')
|
||||
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();
|
||||
self.fetchMetadata();
|
||||
self.parseSpeed();
|
||||
} else {
|
||||
console.log(
|
||||
'[Video info]: Change player mode to HTML5.'
|
||||
@@ -436,50 +492,23 @@ function (VideoPlayer) {
|
||||
|
||||
// In-browser HTML5 player does not support quality
|
||||
// control.
|
||||
_this.el.find('a.quality_control').hide();
|
||||
el.find('a.quality_control').hide();
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
'[Video info]: Start player in YouTube mode.'
|
||||
);
|
||||
|
||||
_this.fetchMetadata();
|
||||
_this.parseSpeed();
|
||||
self.fetchMetadata();
|
||||
self.parseSpeed();
|
||||
}
|
||||
|
||||
_setConfigurations(_this);
|
||||
_renderElements(_this);
|
||||
_setConfigurations(self);
|
||||
_renderElements(self);
|
||||
});
|
||||
}
|
||||
|
||||
return this.__dfd__.promise();
|
||||
}
|
||||
|
||||
/*
|
||||
* function checkStartEndTimes()
|
||||
*
|
||||
* Validate config.startTime and config.endTime times.
|
||||
*
|
||||
* We can check at this time if the times are proper integers, and if they
|
||||
* make general sense. I.e. if start time is => 0 and <= end time.
|
||||
*
|
||||
* An invalid start time will be reset to 0. An invalid end time will be
|
||||
* set to `null`. It the task for the appropriate player API to figure out
|
||||
* if start time and/or end time are greater than the length of the video.
|
||||
*/
|
||||
function checkStartEndTimes() {
|
||||
this.config.startTime = parseInt(this.config.startTime, 10);
|
||||
if (!isFinite(this.config.startTime) || this.config.startTime < 0) {
|
||||
this.config.startTime = 0;
|
||||
}
|
||||
|
||||
this.config.endTime = parseInt(this.config.endTime, 10);
|
||||
if (
|
||||
!isFinite(this.config.endTime) ||
|
||||
this.config.endTime <= this.config.startTime
|
||||
) {
|
||||
this.config.endTime = null;
|
||||
}
|
||||
return __dfd__.promise();
|
||||
}
|
||||
|
||||
// function parseYoutubeStreams(state, youtubeStreams)
|
||||
@@ -595,22 +624,32 @@ function (VideoPlayer) {
|
||||
this.speeds = ($.map(this.videos, function (url, speed) {
|
||||
return speed;
|
||||
})).sort();
|
||||
|
||||
this.setSpeed($.cookie('video_speed'));
|
||||
}
|
||||
|
||||
function setSpeed(newSpeed, updateCookie) {
|
||||
if (_.indexOf(this.speeds, newSpeed) !== -1) {
|
||||
function setSpeed(newSpeed, updateStorage) {
|
||||
// Possible speeds for each player type.
|
||||
// flash = [0.75, 1, 1.25, 1.5]
|
||||
// html5 = [0.75, 1, 1.25, 1.5]
|
||||
// youtube html5 = [0.25, 0.5, 1, 1.5, 2]
|
||||
var map = {
|
||||
'0.25': '0.75',
|
||||
'0.50': '0.75',
|
||||
'0.75': '0.50',
|
||||
'1.25': '1.50',
|
||||
'2.0': '1.50'
|
||||
},
|
||||
useSession = true;
|
||||
|
||||
if (_.contains(this.speeds, newSpeed)) {
|
||||
this.speed = newSpeed;
|
||||
} else {
|
||||
this.speed = '1.0';
|
||||
newSpeed = map[newSpeed];
|
||||
this.speed = _.contains(this.speeds, newSpeed) ? newSpeed : '1.0';
|
||||
}
|
||||
|
||||
if (updateCookie) {
|
||||
$.cookie('video_speed', this.speed, {
|
||||
expires: 3650,
|
||||
path: '/'
|
||||
});
|
||||
if (updateStorage) {
|
||||
this.storage.setItem('video_speed_' + this.id, this.speed, useSession);
|
||||
this.storage.setItem('general_speed', this.speed, useSession);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,7 +687,11 @@ function (VideoPlayer) {
|
||||
}
|
||||
|
||||
function getDuration() {
|
||||
return this.metadata[this.youtubeId()].duration;
|
||||
try {
|
||||
return this.metadata[this.youtubeId()].duration;
|
||||
} catch (err) {
|
||||
return this.metadata[this.youtubeId('1.0')].duration;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -662,8 +705,9 @@ function (VideoPlayer) {
|
||||
*
|
||||
* state.videoPlayer.pause({'param1': 10});
|
||||
*/
|
||||
function trigger(objChain, extraParameters) {
|
||||
var i, tmpObj, chain;
|
||||
function trigger(objChain) {
|
||||
var extraParameters = Array.prototype.slice.call(arguments, 1),
|
||||
i, tmpObj, chain;
|
||||
|
||||
// Remember that 'this' is the 'state' object.
|
||||
tmpObj = this;
|
||||
@@ -685,7 +729,7 @@ function (VideoPlayer) {
|
||||
}
|
||||
}
|
||||
|
||||
tmpObj(extraParameters);
|
||||
tmpObj.apply(this, extraParameters);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ function (HTML5Video, Resizer) {
|
||||
}
|
||||
}
|
||||
|
||||
function onSpeedChange(newSpeed, updateCookie) {
|
||||
function onSpeedChange(newSpeed) {
|
||||
var time = this.videoPlayer.currentTime,
|
||||
methodName, youtubeId;
|
||||
|
||||
@@ -347,7 +347,7 @@ function (HTML5Video, Resizer) {
|
||||
}
|
||||
);
|
||||
|
||||
this.setSpeed(newSpeed, updateCookie);
|
||||
this.setSpeed(newSpeed, true);
|
||||
|
||||
if (
|
||||
this.currentPlayerMode === 'html5' &&
|
||||
@@ -376,6 +376,15 @@ function (HTML5Video, Resizer) {
|
||||
}
|
||||
|
||||
this.el.trigger('speedchange', arguments);
|
||||
|
||||
$.ajax({
|
||||
url: this.config.saveStateUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
speed: newSpeed
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Every 200 ms, if the video is playing, we call the function update, via
|
||||
@@ -434,7 +443,7 @@ function (HTML5Video, Resizer) {
|
||||
end: true
|
||||
});
|
||||
|
||||
if (this.config.show_captions) {
|
||||
if (this.config.showCaptions) {
|
||||
this.trigger('videoCaption.pause', null);
|
||||
}
|
||||
|
||||
@@ -466,7 +475,7 @@ function (HTML5Video, Resizer) {
|
||||
|
||||
this.trigger('videoControl.pause', null);
|
||||
|
||||
if (this.config.show_captions) {
|
||||
if (this.config.showCaptions) {
|
||||
this.trigger('videoCaption.pause', null);
|
||||
}
|
||||
|
||||
@@ -495,7 +504,7 @@ function (HTML5Video, Resizer) {
|
||||
end: false
|
||||
});
|
||||
|
||||
if (this.config.show_captions) {
|
||||
if (this.config.showCaptions) {
|
||||
this.trigger('videoCaption.play', null);
|
||||
}
|
||||
|
||||
@@ -579,7 +588,6 @@ function (HTML5Video, Resizer) {
|
||||
var key = value.toFixed(2).replace(/\.00$/, '.0');
|
||||
|
||||
_this.videos[key] = baseSpeedSubs;
|
||||
|
||||
_this.speeds.push(key);
|
||||
});
|
||||
|
||||
@@ -590,8 +598,8 @@ function (HTML5Video, Resizer) {
|
||||
currentSpeed: this.speed
|
||||
}
|
||||
);
|
||||
|
||||
this.setSpeed($.cookie('video_speed'));
|
||||
this.setSpeed(this.speed);
|
||||
this.trigger('videoSpeedControl.setSpeed', this.speed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ function () {
|
||||
}
|
||||
|
||||
function captionURL() {
|
||||
return '' + this.config.caption_asset_path +
|
||||
return '' + this.config.captionAssetPath +
|
||||
this.youtubeId('1.0') + '.srt.sjson';
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ function () {
|
||||
_this = this,
|
||||
autohideHtml5 = this.config.autohideHtml5;
|
||||
|
||||
this.elVideoWrapper.after(this.videoCaption.subtitlesEl);
|
||||
this.container.after(this.videoCaption.subtitlesEl);
|
||||
this.el.find('.video-controls .secondary-controls')
|
||||
.append(this.videoCaption.hideSubtitlesEl);
|
||||
|
||||
@@ -745,7 +745,7 @@ function () {
|
||||
0.5 * this.videoControl.sliderEl.height() -
|
||||
2 * paddingTop;
|
||||
} else {
|
||||
return this.elVideoWrapper.height();
|
||||
return this.container.height();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import datetime
|
||||
import copy
|
||||
from webob import Response
|
||||
|
||||
from django.http import Http404
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.x_module import XModule, module_attr
|
||||
@@ -31,7 +30,7 @@ from xmodule.contentstore.django import contentstore
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope, String, Boolean, List, Integer, ScopeIds
|
||||
from xblock.fields import Scope, String, Float, Boolean, List, Integer, ScopeIds
|
||||
from xmodule.fields import RelativeTime
|
||||
|
||||
from xmodule.modulestore.inheritance import InheritanceKeyValueStore
|
||||
@@ -137,6 +136,15 @@ class VideoFields(object):
|
||||
scope=Scope.settings,
|
||||
default=""
|
||||
)
|
||||
speed = Float(
|
||||
help="The last speed that was explicitly set by user for the video.",
|
||||
scope=Scope.user_state,
|
||||
)
|
||||
global_speed = Float(
|
||||
help="Default speed in cases when speed wasn't explicitly for specific video",
|
||||
scope=Scope.preferences,
|
||||
default=1.0
|
||||
)
|
||||
|
||||
|
||||
class VideoModule(VideoFields, XModule):
|
||||
@@ -178,10 +186,21 @@ class VideoModule(VideoFields, XModule):
|
||||
js_module_name = "Video"
|
||||
|
||||
def handle_ajax(self, dispatch, data):
|
||||
"""This is not being called right now and we raise 404 error."""
|
||||
ACCEPTED_KEYS = ['speed']
|
||||
|
||||
if dispatch == 'save_user_state':
|
||||
for key in data:
|
||||
if hasattr(self, key) and key in ACCEPTED_KEYS:
|
||||
setattr(self, key, json.loads(data[key]))
|
||||
if key == 'speed':
|
||||
self.global_speed = self.speed
|
||||
|
||||
return json.dumps({'success': True})
|
||||
|
||||
log.debug(u"GET {0}".format(data))
|
||||
log.debug(u"DISPATCH {0}".format(dispatch))
|
||||
raise Http404()
|
||||
|
||||
raise NotFoundError('Unexpected dispatch type')
|
||||
|
||||
def get_html(self):
|
||||
track_url = None
|
||||
@@ -203,24 +222,26 @@ class VideoModule(VideoFields, XModule):
|
||||
track_url = self.runtime.handler_url(self, 'download_transcript')
|
||||
|
||||
return self.system.render_template('video.html', {
|
||||
'youtube_streams': _create_youtube_string(self),
|
||||
'id': self.location.html_id(),
|
||||
'sub': self.sub,
|
||||
'sources': sources,
|
||||
'track': track_url,
|
||||
'display_name': self.display_name_with_default,
|
||||
'ajax_url': self.system.ajax_url + '/save_user_state',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
# This won't work when we move to data that
|
||||
# isn't on the filesystem
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'display_name': self.display_name_with_default,
|
||||
'caption_asset_path': caption_asset_path,
|
||||
'show_captions': json.dumps(self.show_captions),
|
||||
'start': self.start_time.total_seconds(),
|
||||
'end': self.end_time.total_seconds(),
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
'id': self.location.html_id(),
|
||||
'show_captions': json.dumps(self.show_captions),
|
||||
'sources': sources,
|
||||
'speed': self.speed or self.global_speed,
|
||||
'start': self.start_time.total_seconds(),
|
||||
'sub': self.sub,
|
||||
'track': track_url,
|
||||
'youtube_streams': _create_youtube_string(self),
|
||||
# TODO: Later on the value 1500 should be taken from some global
|
||||
# configuration setting field.
|
||||
'yt_test_timeout': 1500,
|
||||
'yt_test_url': settings.YOUTUBE_TEST_URL
|
||||
'yt_test_url': settings.YOUTUBE_TEST_URL,
|
||||
})
|
||||
|
||||
def get_transcript(self, subs_id):
|
||||
|
||||
@@ -45,3 +45,24 @@ Feature: LMS.Video component
|
||||
Given the course has a Video component in HTML5_Unsupported_Video mode
|
||||
Then error message is shown
|
||||
And error message has correct text
|
||||
|
||||
# 8
|
||||
Scenario: Video component stores speed correctly when each video is in separate sequence.
|
||||
Given I am registered for the course "test_course"
|
||||
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 "C" in "Youtube" mode in position "3" of sequential
|
||||
And I open the section with videos
|
||||
And I select the "2.0" speed on video "A"
|
||||
And I select the "0.50" speed on video "B"
|
||||
When I open video "C"
|
||||
Then video "C" should start playing at speed "0.50"
|
||||
When I open video "A"
|
||||
Then video "A" should start playing at speed "2.0"
|
||||
And I reload the page
|
||||
When I open video "A"
|
||||
Then video "A" should start playing at speed "2.0"
|
||||
When I open video "B"
|
||||
Then video "B" should start playing at speed "0.50"
|
||||
When I open video "C"
|
||||
Then video "C" should start playing at speed "0.50"
|
||||
|
||||
@@ -15,6 +15,9 @@ HTML5_SOURCES_INCORRECT = [
|
||||
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp99'
|
||||
]
|
||||
|
||||
coursenum = 'test_course'
|
||||
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')
|
||||
@@ -22,21 +25,48 @@ def does_not_autoplay(_step, video_type):
|
||||
|
||||
@step('the course has a Video component in (.*) mode$')
|
||||
def view_video(_step, player_mode):
|
||||
coursenum = 'test_course'
|
||||
i_am_registered_for_the_course(step, coursenum)
|
||||
|
||||
i_am_registered_for_the_course(_step, coursenum)
|
||||
|
||||
# Make sure we have a video
|
||||
add_video_to_course(coursenum, player_mode.lower())
|
||||
visit_scenario_item('SECTION')
|
||||
|
||||
|
||||
def add_video_to_course(course, player_mode):
|
||||
@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(), 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, display_name='Video'):
|
||||
category = 'video'
|
||||
|
||||
kwargs = {
|
||||
'parent_location': section_location(course),
|
||||
'category': category,
|
||||
'display_name': 'Video'
|
||||
'display_name': display_name
|
||||
}
|
||||
|
||||
if player_mode == 'html5':
|
||||
@@ -112,3 +142,12 @@ def error_message_has_correct_text(_step):
|
||||
assert world.css_has_text(selector, text)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -19,6 +19,7 @@ from xmodule.exceptions import NotFoundError
|
||||
|
||||
class TestVideo(BaseTestXmodule):
|
||||
"""Integration tests: web client + mongo."""
|
||||
|
||||
CATEGORY = "video"
|
||||
DATA = SOURCE_XML
|
||||
METADATA = {}
|
||||
@@ -57,6 +58,7 @@ class TestVideoYouTube(TestVideo):
|
||||
}
|
||||
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'caption_asset_path': '/static/subs/',
|
||||
'show_captions': 'true',
|
||||
@@ -64,6 +66,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'end': 3610.0,
|
||||
'id': self.item_module.location.html_id(),
|
||||
'sources': sources,
|
||||
'speed': 1.0,
|
||||
'start': 3603.0,
|
||||
'sub': u'a_sub_file.srt.sjson',
|
||||
'track': None,
|
||||
@@ -75,7 +78,7 @@ class TestVideoYouTube(TestVideo):
|
||||
|
||||
self.assertEqual(
|
||||
context,
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context),
|
||||
)
|
||||
|
||||
|
||||
@@ -93,9 +96,10 @@ class TestVideoNonYouTube(TestVideo):
|
||||
</video>
|
||||
"""
|
||||
MODEL_DATA = {
|
||||
'data': DATA
|
||||
'data': DATA,
|
||||
}
|
||||
METADATA = {}
|
||||
|
||||
def test_video_constructor(self):
|
||||
"""Make sure that if the 'youtube' attribute is omitted in XML, then
|
||||
the template generates an empty string for the YouTube streams.
|
||||
@@ -107,8 +111,8 @@ class TestVideoNonYouTube(TestVideo):
|
||||
}
|
||||
|
||||
context = self.item_module.render('student_view').content
|
||||
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'caption_asset_path': '/static/subs/',
|
||||
'show_captions': 'true',
|
||||
@@ -116,6 +120,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
'end': 3610.0,
|
||||
'id': self.item_module.location.html_id(),
|
||||
'sources': sources,
|
||||
'speed': 1.0,
|
||||
'start': 3603.0,
|
||||
'sub': u'a_sub_file.srt.sjson',
|
||||
'track': None,
|
||||
@@ -127,7 +132,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
|
||||
self.assertEqual(
|
||||
context,
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context),
|
||||
)
|
||||
|
||||
|
||||
@@ -137,6 +142,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'''
|
||||
CATEGORY = "video"
|
||||
DATA = SOURCE_XML
|
||||
maxDiff = None
|
||||
METADATA = {}
|
||||
|
||||
def setUp(self):
|
||||
@@ -195,7 +201,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
},
|
||||
'start': 3603.0,
|
||||
'sub': u'a_sub_file.srt.sjson',
|
||||
'track': '',
|
||||
'speed': 1.0,
|
||||
'track': None,
|
||||
'youtube_streams': '1.00:OEoXaMPEzfM',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
|
||||
'yt_test_timeout': 1500,
|
||||
@@ -212,16 +219,18 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
self.initialize_module(data=DATA)
|
||||
track_url = self.item_descriptor.xmodule_runtime.handler_url(self.item_module, 'download_transcript')
|
||||
|
||||
context = self.item_module.render('student_view').content
|
||||
|
||||
expected_context.update({
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'track': track_url if data['expected_track_url'] == u'a_sub_file.srt.sjson' else data['expected_track_url'],
|
||||
'sub': data['sub'],
|
||||
'id': self.item_module.location.html_id(),
|
||||
})
|
||||
|
||||
context = self.item_module.render('student_view').content
|
||||
self.assertEqual(
|
||||
context,
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context),
|
||||
)
|
||||
|
||||
def test_get_html_source(self):
|
||||
@@ -293,6 +302,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'end': 3610.0,
|
||||
'id': None,
|
||||
'sources': None,
|
||||
'speed': 1.0,
|
||||
'start': 3603.0,
|
||||
'sub': u'a_sub_file.srt.sjson',
|
||||
'track': None,
|
||||
@@ -309,14 +319,14 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=data['sources']
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
context = self.item_module.render('student_view').content
|
||||
|
||||
expected_context.update({
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'sources': data['result'],
|
||||
'id': self.item_module.location.html_id(),
|
||||
})
|
||||
|
||||
context = self.item_module.render('student_view').content
|
||||
|
||||
self.assertEqual(
|
||||
context,
|
||||
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
|
||||
|
||||
@@ -15,11 +15,7 @@ common/lib/xmodule/xmodule/modulestore/tests/factories.py to create the
|
||||
course, section, subsection, unit, etc.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.video_module import VideoDescriptor, _create_youtube_string
|
||||
from xmodule.video_module import VideoDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.tests import get_test_system, LogicTest, get_test_descriptor_system
|
||||
from xblock.field_data import DictFieldData
|
||||
@@ -63,40 +59,6 @@ class VideoFactory(object):
|
||||
return descriptor
|
||||
|
||||
|
||||
class VideoModuleUnitTest(unittest.TestCase):
|
||||
"""Unit tests for Video Xmodule."""
|
||||
def test_video_get_html(self):
|
||||
"""Make sure that all parameters extracted correclty from xml"""
|
||||
module = VideoFactory.create()
|
||||
sources = {
|
||||
'main': 'example.mp4',
|
||||
'mp4': 'example.mp4',
|
||||
'webm': 'example.webm',
|
||||
}
|
||||
|
||||
expected_context = {
|
||||
'caption_asset_path': '/static/subs/',
|
||||
'sub': 'a_sub_file.srt.sjson',
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'display_name': 'A Name',
|
||||
'end': 3610.0,
|
||||
'start': 3603.0,
|
||||
'id': module.location.html_id(),
|
||||
'show_captions': 'true',
|
||||
'sources': sources,
|
||||
'youtube_streams': _create_youtube_string(module),
|
||||
'track': None,
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
'yt_test_timeout': 1500,
|
||||
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
|
||||
}
|
||||
|
||||
self.assertEqual(
|
||||
module.render('student_view').content,
|
||||
module.runtime.render_template('video.html', expected_context)
|
||||
)
|
||||
|
||||
|
||||
class VideoModuleLogicTest(LogicTest):
|
||||
"""Tests for logic of Video Xmodule."""
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
${'data-webm-source="{}"'.format(sources.get('webm')) if sources.get('webm') else ''}
|
||||
${'data-ogg-source="{}"'.format(sources.get('ogv')) if sources.get('ogv') else ''}
|
||||
|
||||
data-save-state-url="${ajax_url}"
|
||||
data-caption-data-dir="${data_dir}"
|
||||
data-show-captions="${show_captions}"
|
||||
data-speed="${speed}"
|
||||
data-start="${start}"
|
||||
data-end="${end}"
|
||||
data-caption-asset-path="${caption_asset_path}"
|
||||
@@ -108,5 +110,3 @@
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user