Merge pull request #3950 from edx/anton/transcript-editor-disable-validation
Allow the video player to work with links with redirection.
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: Fix bug with incorrect link format and redirection. BLD-1049
|
||||
|
||||
Blades: Fix bug with incorrect RelativeTime value after XML serialization. BLD-1060
|
||||
|
||||
LMS: Update bulk email implementation to lessen load on the database
|
||||
|
||||
@@ -34,14 +34,22 @@ Feature: CMS Transcripts
|
||||
And I expect inputs are enabled
|
||||
|
||||
#User input URL with incorrect format
|
||||
And I enter a "htt://link.c" source to field number 1
|
||||
And I enter a "http://link.c" source to field number 1
|
||||
Then I see error message "url_format"
|
||||
# Currently we are working with 1st field. It means, that if 1st field
|
||||
# contain incorrect value, 2nd and 3rd fields should be disabled until
|
||||
# 1st field will be filled by correct correct value
|
||||
And I expect 2, 3 inputs are disabled
|
||||
# We are not clearing fields here,
|
||||
# Because we changing same field.
|
||||
|
||||
#User input URL with incorrect format
|
||||
And I enter a "http://goo.gl/pxxZrg" source to field number 1
|
||||
And I enter a "http://goo.gl/pxxZrg" source to field number 2
|
||||
Then I see error message "links_duplication"
|
||||
And I expect 1, 3 inputs are disabled
|
||||
|
||||
And I clear fields
|
||||
And I expect inputs are enabled
|
||||
|
||||
And I enter a "http://youtu.be/t_not_exist" source to field number 1
|
||||
Then I do not see error message
|
||||
And I expect inputs are enabled
|
||||
@@ -699,3 +707,37 @@ Feature: CMS Transcripts
|
||||
|
||||
And I edit the component
|
||||
Then I see status message "found"
|
||||
|
||||
#37 Uploading subtitles with different file name than file
|
||||
Scenario: Shortened link: File name and name of subs are different
|
||||
Given I have created a Video component
|
||||
And I edit the component
|
||||
|
||||
And I enter a "http://goo.gl/pxxZrg" source to field number 1
|
||||
And I see status message "not found"
|
||||
And I upload the transcripts file "uk_transcripts.srt"
|
||||
Then I see status message "uploaded_successfully"
|
||||
And I see value "pxxZrg" in the field "Default Timed Transcript"
|
||||
|
||||
And I save changes
|
||||
Then when I view the video it does show the captions
|
||||
|
||||
And I edit the component
|
||||
Then I see status message "found"
|
||||
|
||||
#38 Uploading subtitles with different file name than file
|
||||
Scenario: Relative link: File name and name of subs are different
|
||||
Given I have created a Video component
|
||||
And I edit the component
|
||||
|
||||
And I enter a "/gizmo.webm" source to field number 1
|
||||
And I see status message "not found"
|
||||
And I upload the transcripts file "uk_transcripts.srt"
|
||||
Then I see status message "uploaded_successfully"
|
||||
And I see value "gizmo" in the field "Default Timed Transcript"
|
||||
|
||||
And I save changes
|
||||
Then when I view the video it does show the captions
|
||||
|
||||
And I edit the component
|
||||
Then I see status message "found"
|
||||
|
||||
@@ -19,6 +19,7 @@ DELAY = 0.5
|
||||
ERROR_MESSAGES = {
|
||||
'url_format': u'Incorrect url format.',
|
||||
'file_type': u'Link types should be unique.',
|
||||
'links_duplication': u'Links should be unique.',
|
||||
}
|
||||
|
||||
STATUSES = {
|
||||
@@ -43,7 +44,7 @@ TRANSCRIPTS_BUTTONS = {
|
||||
'import': ('.setting-import', 'Import YouTube Transcript'),
|
||||
'download_to_edit': ('.setting-download', 'Download Transcript for Editing'),
|
||||
'disabled_download_to_edit': ('.setting-download.is-disabled', 'Download Transcript for Editing'),
|
||||
'upload_new_timed_transcripts': ('.setting-upload', 'Upload New Transcript'),
|
||||
'upload_new_timed_transcripts': ('.setting-upload', 'Upload New Transcript'),
|
||||
'replace': ('.setting-replace', 'Yes, replace the edX transcript with the YouTube transcript'),
|
||||
'choose': ('.setting-choose', 'Timed Transcript from {}'),
|
||||
'use_existing': ('.setting-use-existing', 'Use Current Transcript'),
|
||||
@@ -118,8 +119,7 @@ def i_see_status_message(_step, status):
|
||||
assert world.css_has_text(SELECTORS['status_bar'], STATUSES[status])
|
||||
|
||||
DOWNLOAD_BUTTON = TRANSCRIPTS_BUTTONS["download_to_edit"][0]
|
||||
if world.is_css_present(DOWNLOAD_BUTTON, wait_time=1) \
|
||||
and not world.css_find(DOWNLOAD_BUTTON)[0].has_class('is-disabled'):
|
||||
if world.is_css_present(DOWNLOAD_BUTTON, wait_time=1) and not world.css_find(DOWNLOAD_BUTTON)[0].has_class('is-disabled'):
|
||||
assert _transcripts_are_downloaded()
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ def check_text_in_the_captions(_step, text):
|
||||
@step('I see value "([^"]*)" in the field "([^"]*)"$')
|
||||
def check_transcripts_field(_step, values, field_name):
|
||||
world.select_editor_tab('Advanced')
|
||||
tab = world.css_find('#settings-tab').first;
|
||||
tab = world.css_find('#settings-tab').first
|
||||
field_id = '#' + tab.find_by_xpath('.//label[text()="%s"]' % field_name.strip())[0]['for']
|
||||
values_list = [i.strip() == world.css_value(field_id) for i in values.split('|')]
|
||||
assert any(values_list)
|
||||
@@ -229,19 +229,19 @@ def open_tab(_step, tab_name):
|
||||
|
||||
@step('I set value "([^"]*)" to the field "([^"]*)"$')
|
||||
def set_value_transcripts_field(_step, value, field_name):
|
||||
tab = world.css_find('#settings-tab').first;
|
||||
tab = world.css_find('#settings-tab').first
|
||||
XPATH = './/label[text()="{name}"]'.format(name=field_name)
|
||||
SELECTOR = '#' + tab.find_by_xpath(XPATH)[0]['for']
|
||||
element = world.css_find(SELECTOR).first
|
||||
if element['type'] == 'text':
|
||||
SCRIPT = '$("{selector}").val("{value}").change()'.format(
|
||||
selector=SELECTOR,
|
||||
value=value
|
||||
)
|
||||
selector=SELECTOR,
|
||||
value=value
|
||||
)
|
||||
world.browser.execute_script(SCRIPT)
|
||||
assert world.css_has_value(SELECTOR, value)
|
||||
else:
|
||||
assert False, 'Incorrect element type.';
|
||||
assert False, 'Incorrect element type.'
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('Transcripts.Utils', function () {
|
||||
} (videoId)),
|
||||
html5FileName = 'file_name',
|
||||
html5LinksList = (function (videoName) {
|
||||
var videoTypes = ['mp4', 'webm'],
|
||||
var videoTypes = ['mp4', 'webm', 'm4v', 'ogv'],
|
||||
links = [
|
||||
'http://somelink.com/%s.%s?param=1¶m=2#hash',
|
||||
'http://somelink.com/%s.%s#hash',
|
||||
@@ -34,6 +34,7 @@ describe('Transcripts.Utils', function () {
|
||||
'http://somelink.com/%s.%s',
|
||||
'ftp://somelink.com/%s.%s',
|
||||
'https://somelink.com/%s.%s',
|
||||
'https://somelink.com/sub/sub/%s.%s',
|
||||
'http://cdn.somecdn.net/v/%s.%s',
|
||||
'somelink.com/%s.%s',
|
||||
'%s.%s'
|
||||
@@ -48,7 +49,25 @@ describe('Transcripts.Utils', function () {
|
||||
|
||||
return data;
|
||||
|
||||
} (html5FileName));
|
||||
} (html5FileName)),
|
||||
otherLinkId = 'other_link_id',
|
||||
otherLinksList = (function (linkId) {
|
||||
var links = [
|
||||
'http://goo.gl/%s?param=1¶m=2#hash',
|
||||
'http://goo.gl/%s?param=1¶m=2',
|
||||
'http://goo.gl/%s#hash',
|
||||
'http://goo.gl/%s',
|
||||
'http://goo.gl/%s',
|
||||
'ftp://goo.gl/%s',
|
||||
'https://goo.gl/%s',
|
||||
'%s'
|
||||
];
|
||||
|
||||
return $.map(links, function (link) {
|
||||
return _str.sprintf(link, linkId);
|
||||
});
|
||||
|
||||
} (otherLinkId));
|
||||
|
||||
describe('Method: getField', function (){
|
||||
var collection,
|
||||
@@ -107,7 +126,6 @@ describe('Transcripts.Utils', function () {
|
||||
});
|
||||
|
||||
describe('Wrong arguments ', function () {
|
||||
|
||||
beforeEach(function(){
|
||||
spyOn(console, 'log');
|
||||
});
|
||||
@@ -124,18 +142,9 @@ describe('Transcripts.Utils', function () {
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('videoId is wrong', function () {
|
||||
var videoId = 'wrong_id',
|
||||
link = 'http://youtu.be/' + videoId,
|
||||
result = Utils.parseYoutubeLink(link);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
var wrongUrls = [
|
||||
'http://youtu.bee/' + videoId,
|
||||
'http://youtu.be/',
|
||||
'example.com',
|
||||
'/static/example',
|
||||
'http://google.com/somevideo.mp4'
|
||||
];
|
||||
|
||||
@@ -163,10 +172,20 @@ describe('Transcripts.Utils', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$.each(otherLinksList, function (index, link) {
|
||||
it(link, function () {
|
||||
var result = Utils.parseHTML5Link(link);
|
||||
|
||||
expect(result).toEqual({
|
||||
video: otherLinkId,
|
||||
type: 'other'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Wrong arguments ', function () {
|
||||
|
||||
beforeEach(function(){
|
||||
spyOn(console, 'log');
|
||||
});
|
||||
@@ -184,15 +203,11 @@ describe('Transcripts.Utils', function () {
|
||||
});
|
||||
|
||||
var html5WrongUrls = [
|
||||
'http://youtu.bee/' + videoId,
|
||||
'http://youtu.be/',
|
||||
'example.com',
|
||||
'http://google.com/somevideo.mp1',
|
||||
'http://google.com/somevideomp4',
|
||||
'http://google.com/somevideo_mp4',
|
||||
'http://google.com/somevideo:mp4',
|
||||
'http://google.com/somevideo',
|
||||
'http://google.com/somevideo.webm_'
|
||||
'http://example.com/.mp4',
|
||||
'http://example.com/video_name.',
|
||||
'http://example.com/',
|
||||
'http://example.com'
|
||||
];
|
||||
|
||||
$.each(html5WrongUrls, function (index, link) {
|
||||
@@ -248,6 +263,13 @@ describe('Transcripts.Utils', function () {
|
||||
});
|
||||
|
||||
describe('Wrong arguments ', function () {
|
||||
it('youtube videoId is wrong', function () {
|
||||
var videoId = 'wrong_id',
|
||||
link = 'http://youtu.be/' + videoId,
|
||||
result = Utils.parseLink(link);
|
||||
|
||||
expect(result).toEqual({ mode : 'incorrect' });
|
||||
});
|
||||
|
||||
it('no arguments', function () {
|
||||
var result = Utils.parseLink();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
define(
|
||||
[
|
||||
"jquery", "underscore",
|
||||
"js/views/video/transcripts/utils", "js/views/video/transcripts/metadata_videolist",
|
||||
"js/views/metadata", "js/models/metadata", "js/views/abstract_editor",
|
||||
"sinon", "xmodule", "jasmine-jquery"
|
||||
'jquery', 'underscore',
|
||||
'js/views/video/transcripts/utils',
|
||||
'js/views/video/transcripts/metadata_videolist', 'js/models/metadata',
|
||||
'js/views/abstract_editor',
|
||||
'sinon', 'xmodule', 'jasmine-jquery'
|
||||
],
|
||||
function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, sinon) {
|
||||
function ($, _, Utils, VideoList, MetadataModel, AbstractEditor, sinon) {
|
||||
'use strict';
|
||||
describe('CMS.Views.Metadata.VideoList', function () {
|
||||
var videoListEntryTemplate = readFixtures(
|
||||
'video/transcripts/metadata-videolist-entry.underscore'
|
||||
@@ -14,19 +16,19 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
component_locator = 'component_locator',
|
||||
videoList = [
|
||||
{
|
||||
mode: "youtube",
|
||||
type: "youtube",
|
||||
video: "12345678901"
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
},
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: "html5",
|
||||
type: "webm",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'webm',
|
||||
video: 'video'
|
||||
}
|
||||
],
|
||||
modelStub = {
|
||||
@@ -54,7 +56,7 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
sinonXhr = sinon.fakeServer.create();
|
||||
sinonXhr.respondWith([
|
||||
200,
|
||||
{ "Content-Type": "application/json"},
|
||||
{ 'Content-Type': 'application/json'},
|
||||
response
|
||||
]);
|
||||
|
||||
@@ -65,15 +67,15 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
'data-locator': component_locator
|
||||
}),
|
||||
model = new MetadataModel(modelStub),
|
||||
videoList, $el;
|
||||
$el;
|
||||
|
||||
setFixtures(tpl);
|
||||
|
||||
appendSetFixtures(
|
||||
$("<script>",
|
||||
$('<script>',
|
||||
{
|
||||
id: "metadata-videolist-entry",
|
||||
type: "text/template"
|
||||
id: 'metadata-videolist-entry',
|
||||
type: 'text/template'
|
||||
}
|
||||
).text(videoListEntryTemplate)
|
||||
);
|
||||
@@ -147,7 +149,7 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
}
|
||||
|
||||
return flag;
|
||||
}, "Ajax Timeout", 750);
|
||||
}, 'Ajax Timeout', 750);
|
||||
|
||||
runs(expectFunc);
|
||||
};
|
||||
@@ -189,21 +191,21 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
it('is rendered with opened extra videos bar', function () {
|
||||
var videoListLength = [
|
||||
{
|
||||
mode: "youtube",
|
||||
type: "youtube",
|
||||
video: "12345678901"
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
},
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
}
|
||||
],
|
||||
videoListHtml5mode = [
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -240,9 +242,9 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
it('is rendered without opened extra videos bar', function () {
|
||||
var videoList = [
|
||||
{
|
||||
mode: "youtube",
|
||||
type: "youtube",
|
||||
video: "12345678901"
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -263,6 +265,59 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
|
||||
});
|
||||
|
||||
describe('isUniqOtherVideos', function () {
|
||||
it('Unique data - return true', function () {
|
||||
var data = videoList.concat([{
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
}]);
|
||||
|
||||
waitsForResponse(function () {
|
||||
var result = view.isUniqOtherVideos(data);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('Not Unique data - return false', function () {
|
||||
var data = [
|
||||
{
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
},
|
||||
{
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
},
|
||||
{
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
}
|
||||
];
|
||||
|
||||
waitsForResponse(function () {
|
||||
var result = view.isUniqOtherVideos(data);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUniqVideoTypes', function () {
|
||||
|
||||
it('Unique data - return true', function () {
|
||||
@@ -279,19 +334,24 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
it('Not Unique data - return false', function () {
|
||||
var data = [
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: "youtube",
|
||||
type: "youtube",
|
||||
video: "12345678901"
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
},
|
||||
{
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -304,23 +364,27 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
});
|
||||
|
||||
describe('checkIsUniqVideoTypes', function () {
|
||||
|
||||
it('Error is shown', function () {
|
||||
var data = [
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: "html5",
|
||||
type: "mp4",
|
||||
video: "video"
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: "youtube",
|
||||
type: "youtube",
|
||||
video: "12345678901"
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
},
|
||||
{
|
||||
mode: 'youtube',
|
||||
type: 'youtube',
|
||||
video: '12345678901'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -350,7 +414,7 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
spyOn(view, 'checkIsUniqVideoTypes').andReturn(true);
|
||||
});
|
||||
|
||||
it('Error message are shown', function () {
|
||||
it('Error message is shown', function () {
|
||||
waitsForResponse(function () {
|
||||
var data = { mode: 'incorrect' },
|
||||
result = view.checkValidity(data, true);
|
||||
@@ -361,7 +425,7 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
});
|
||||
});
|
||||
|
||||
it('Error message are shown when flag is not passed', function () {
|
||||
it('Error message is shown when flag is not passed', function () {
|
||||
waitsForResponse(function () {
|
||||
var data = { mode: 'incorrect' },
|
||||
result = view.checkValidity(data);
|
||||
@@ -435,6 +499,11 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
mode: 'html5',
|
||||
type: 'mp4',
|
||||
video: 'video'
|
||||
},
|
||||
{
|
||||
mode: 'html5',
|
||||
type: 'other',
|
||||
video: 'pxxZrg'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -442,6 +511,7 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
view.setValueInEditor([
|
||||
'http://youtu.be/12345678901',
|
||||
'video.mp4',
|
||||
'http://goo.gl/pxxZrg',
|
||||
'video'
|
||||
]);
|
||||
expect(view).assertIsCorrectVideoList(value);
|
||||
@@ -540,13 +610,17 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
expect(messenger.hideError).not.toHaveBeenCalled();
|
||||
expect(view.updateModel).not.toHaveBeenCalled();
|
||||
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
|
||||
expect($.fn.prop).toHaveBeenCalledWith('disabled', true);
|
||||
expect($.fn.addClass).toHaveBeenCalledWith('is-disabled');
|
||||
expect($.fn.prop).toHaveBeenCalledWith(
|
||||
'disabled', true
|
||||
);
|
||||
expect($.fn.addClass).toHaveBeenCalledWith(
|
||||
'is-disabled'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('Main field has invalid value - extra Videos Bar should be closed',
|
||||
it('Main field has invalid value - extra Videos Bar is closed',
|
||||
function () {
|
||||
$.fn.hasClass.andReturn(true);
|
||||
view.checkValidity.andReturn(false);
|
||||
@@ -556,8 +630,12 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
expect(messenger.hideError).not.toHaveBeenCalled();
|
||||
expect(view.updateModel).not.toHaveBeenCalled();
|
||||
expect(view.closeExtraVideosBar).toHaveBeenCalled();
|
||||
expect($.fn.prop).toHaveBeenCalledWith('disabled', true);
|
||||
expect($.fn.addClass).toHaveBeenCalledWith('is-disabled');
|
||||
expect($.fn.prop).toHaveBeenCalledWith(
|
||||
'disabled', true
|
||||
);
|
||||
expect($.fn.addClass).toHaveBeenCalledWith(
|
||||
'is-disabled'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -572,8 +650,12 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
expect(messenger.hideError).not.toHaveBeenCalled();
|
||||
expect(view.updateModel).toHaveBeenCalled();
|
||||
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
|
||||
expect($.fn.prop).toHaveBeenCalledWith('disabled', false);
|
||||
expect($.fn.removeClass).toHaveBeenCalledWith('is-disabled');
|
||||
expect($.fn.prop).toHaveBeenCalledWith(
|
||||
'disabled', false
|
||||
);
|
||||
expect($.fn.removeClass).toHaveBeenCalledWith(
|
||||
'is-disabled'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -588,8 +670,12 @@ function ($, _, Utils, VideoList, MetadataView, MetadataModel, AbstractEditor, s
|
||||
expect(messenger.hideError).toHaveBeenCalled();
|
||||
expect(view.updateModel).not.toHaveBeenCalled();
|
||||
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
|
||||
expect($.fn.prop).toHaveBeenCalledWith('disabled', false);
|
||||
expect($.fn.removeClass).toHaveBeenCalledWith('is-disabled');
|
||||
expect($.fn.prop).toHaveBeenCalledWith(
|
||||
'disabled', false
|
||||
);
|
||||
expect($.fn.removeClass).toHaveBeenCalledWith(
|
||||
'is-disabled'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
define(
|
||||
[
|
||||
"jquery", "backbone", "underscore", "js/views/abstract_editor",
|
||||
"js/views/video/transcripts/utils", "js/views/video/transcripts/message_manager",
|
||||
"js/views/metadata"
|
||||
'jquery', 'backbone', 'underscore', 'js/views/abstract_editor',
|
||||
'js/views/video/transcripts/utils',
|
||||
'js/views/video/transcripts/message_manager'
|
||||
],
|
||||
function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
function($, Backbone, _, AbstractEditor, Utils, MessageManager) {
|
||||
'use strict';
|
||||
var VideoList = AbstractEditor.extend({
|
||||
// Time that we wait since the last time user typed.
|
||||
inputDelay: 300,
|
||||
@@ -27,10 +28,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
initialize: function () {
|
||||
// Initialize MessageManager that is responsible for
|
||||
// status messages and errors.
|
||||
var Messenger = this.options.MessageManager || MessageManager;
|
||||
|
||||
|
||||
var messenger = this.options.MessageManager || MessageManager;
|
||||
this.messenger = new messenger({
|
||||
this.messenger = new Messenger({
|
||||
el: this.$el,
|
||||
parent: this
|
||||
});
|
||||
@@ -46,7 +46,8 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
_.debounce(_.bind(this.inputHandler, this), this.inputDelay)
|
||||
);
|
||||
|
||||
this.component_locator = this.$el.closest('[data-locator]').data('locator');
|
||||
this.component_locator = this.$el.closest('[data-locator]')
|
||||
.data('locator');
|
||||
},
|
||||
|
||||
render: function () {
|
||||
@@ -55,11 +56,14 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
.apply(this, arguments);
|
||||
|
||||
var self = this,
|
||||
component_locator = this.$el.closest('[data-locator]').data('locator'),
|
||||
component_locator = this.$el.closest('[data-locator]')
|
||||
.data('locator'),
|
||||
videoList = this.getVideoObjectsList(),
|
||||
|
||||
showServerError = function (response) {
|
||||
var errorMessage = response.status || 'Error: Connection with server failed.';
|
||||
var errorMessage = response.status ||
|
||||
'Error: Connection with server failed.';
|
||||
|
||||
self.messenger
|
||||
.render('not_found')
|
||||
.showError(
|
||||
@@ -105,13 +109,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Returns the values currently displayed in the editor/view.
|
||||
*
|
||||
* @returns {array} List of non-empty values.
|
||||
*
|
||||
*/
|
||||
* Returns the values currently displayed in the editor/view.
|
||||
* @return {Array} List of non-empty values.
|
||||
*/
|
||||
getValueFromEditor: function () {
|
||||
return _.map(
|
||||
this.$el.find('.input'),
|
||||
@@ -122,29 +122,25 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Returns list of objects with information about the values currently
|
||||
* displayed in the editor/view.
|
||||
*
|
||||
* @returns {array} List of objects.
|
||||
*
|
||||
* @examples
|
||||
* this.getValueFromEditor(); // =>
|
||||
* [
|
||||
* 'http://youtu.be/OEoXaMPEzfM',
|
||||
* 'video_name.mp4',
|
||||
* 'video_name.webm'
|
||||
* ]
|
||||
*
|
||||
* this.getVideoObjectsList(); // =>
|
||||
* [
|
||||
* {mode: `youtube`, type: `youtube`, ...},
|
||||
* {mode: `html5`, type: `mp4`, ...},
|
||||
* {mode: `html5`, type: `webm`, ...}
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
* Returns list of objects with information about the values currently
|
||||
* displayed in the editor/view.
|
||||
* @return {Array} List of objects.
|
||||
* @examples
|
||||
* this.getValueFromEditor(); // =>
|
||||
* [
|
||||
* 'http://youtu.be/OEoXaMPEzfM',
|
||||
* 'video_name.mp4',
|
||||
* 'video_name.webm'
|
||||
* ]
|
||||
*
|
||||
* this.getVideoObjectsList(); // =>
|
||||
* [
|
||||
* {mode: `youtube`, type: `youtube`, ...},
|
||||
* {mode: `html5`, type: `mp4`, ...},
|
||||
* {mode: `html5`, type: `webm`, ...}
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
getVideoObjectsList: function () {
|
||||
var links = this.getValueFromEditor();
|
||||
|
||||
@@ -152,16 +148,11 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Sets the values currently displayed in the editor/view.
|
||||
*
|
||||
* @params {array} value List of values.
|
||||
*
|
||||
*/
|
||||
* Sets the values currently displayed in the editor/view.
|
||||
* @param {Array} value List of values.
|
||||
*/
|
||||
setValueInEditor: function (value) {
|
||||
var parseLink = Utils.parseLink,
|
||||
list = this.$el.find('.input'),
|
||||
var list = this.$el.find('.input'),
|
||||
val = value.filter(_.identity),
|
||||
placeholders = this.getPlaceholders(val);
|
||||
|
||||
@@ -174,19 +165,15 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Returns the placeholders for the values currently displayed in the
|
||||
* editor/view.
|
||||
*
|
||||
* @returns {array} List of placeholders.
|
||||
*
|
||||
*/
|
||||
* Returns the placeholders for the values currently displayed in the
|
||||
* editor/view.
|
||||
* @return {Array} List of placeholders.
|
||||
*/
|
||||
getPlaceholders: function (value) {
|
||||
var parseLink = Utils.parseLink,
|
||||
placeholders = _.clone(this.placeholders);
|
||||
|
||||
// Returned list should have the same size as a count of editors/views.
|
||||
// Returned list should have the same size as a count of editors.
|
||||
return _.map(
|
||||
this.$el.find('.input'),
|
||||
function (element, index) {
|
||||
@@ -214,13 +201,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Opens video sources box.
|
||||
*
|
||||
* @params {object} event Event object.
|
||||
*
|
||||
*/
|
||||
* Opens video sources box.
|
||||
* @param {Object} event Event object.
|
||||
*/
|
||||
openExtraVideosBar: function (event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
@@ -230,13 +213,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Closes video sources box.
|
||||
*
|
||||
* @params {object} event Event object.
|
||||
*
|
||||
*/
|
||||
* Closes video sources box.
|
||||
* @param {Object} event Event object.
|
||||
*/
|
||||
closeExtraVideosBar: function (event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
@@ -246,13 +225,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Toggles video sources box.
|
||||
*
|
||||
* @params {object} event Event object.
|
||||
*
|
||||
*/
|
||||
* Toggles video sources box.
|
||||
* @param {Object} event Event object.
|
||||
*/
|
||||
toggleExtraVideosBar: function (event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
@@ -266,13 +241,9 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Handle `input` event.
|
||||
*
|
||||
* @params {object} event Event object.
|
||||
*
|
||||
*/
|
||||
* Handle `input` event.
|
||||
* @param {Object} event Event object.
|
||||
*/
|
||||
inputHandler: function (event) {
|
||||
if (event && event.preventDefault) {
|
||||
event.preventDefault();
|
||||
@@ -329,77 +300,108 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager, MetadataView) {
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Checks the values currently displayed in the editor/view have unique
|
||||
* types (mp4 | webm | youtube).
|
||||
*
|
||||
* @param {object} videoList List of objects with information about the
|
||||
* values currently displayed in the editor/view
|
||||
*
|
||||
* @returns {boolean} Boolean value that indicate if video types are unique.
|
||||
*
|
||||
*/
|
||||
* Checks the values currently displayed in the editor/view have unique
|
||||
* types (mp4 | webm | youtube).
|
||||
* @param {Object} videoList List of objects with information about the
|
||||
* @return {Boolean} Boolean value that indicate if video types are
|
||||
* unique.
|
||||
*/
|
||||
isUniqVideoTypes: function (videoList) {
|
||||
// Extract a list of "type" property values.
|
||||
var arr = _.pluck(videoList, 'type'), // => ex: ['youtube', 'mp4', 'mp4']
|
||||
// Produces a duplicate-free version of the array.
|
||||
uniqArr = _.uniq(arr); // => ex: ['youtube', 'mp4']
|
||||
// => ex: ['webm', 'mp4', 'mp4']
|
||||
var arr = _.pluck(videoList, 'type').filter(function (item) {
|
||||
return item !== 'other';
|
||||
}),
|
||||
// Produces a duplicate-free version of the array.
|
||||
uniqArr = _.uniq(arr); // => ex: ['webm', 'mp4']
|
||||
|
||||
return arr.length === uniqArr.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Shows error message if the values currently displayed in the
|
||||
* editor/view have duplicate types.
|
||||
*
|
||||
* @param {object} list List of objects with information about the
|
||||
* values currently displayed in the editor/view
|
||||
*
|
||||
* @returns {boolean} Boolean value that indicate if video types are unique.
|
||||
*
|
||||
*/
|
||||
checkIsUniqVideoTypes: function (list) {
|
||||
var videoList = list || this.getVideoObjectsList(),
|
||||
isUnique = true;
|
||||
* Checks that links without file format are unique.
|
||||
* @param {Object} videoList List of objects with information about the
|
||||
* @return {Boolean} Boolean value that indicate if video types are
|
||||
* unique.
|
||||
*/
|
||||
isUniqOtherVideos: function (videoList) {
|
||||
// Returns list of video objects with "type" equal "other" or
|
||||
// "youtube".
|
||||
var otherLinksList = videoList.filter(function (item) {
|
||||
return item.type === 'other';
|
||||
}),
|
||||
// Extract a list of "video" property values.
|
||||
namesList = _.pluck(otherLinksList, 'video'),
|
||||
// Produces a duplicate-free version of the array.
|
||||
uniqNamesList = _.uniq(namesList);
|
||||
|
||||
if (!this.isUniqVideoTypes(videoList)) {
|
||||
this.messenger
|
||||
.showError('Link types should be unique.', true);
|
||||
|
||||
isUnique = false;
|
||||
}
|
||||
|
||||
return isUnique;
|
||||
return namesList.length === uniqNamesList.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Checks if the values currently displayed in the editor/view have
|
||||
* valid values and show error messages.
|
||||
*
|
||||
* @param {object} data Objects with information about the value
|
||||
* currently displayed in the editor/view
|
||||
*
|
||||
* @param {boolean} showErrorModeMessage Disable mode validation
|
||||
*
|
||||
* @returns {boolean} Boolean value that indicate if value is valid.
|
||||
*
|
||||
*/
|
||||
checkValidity: function (data, showErrorModeMessage) {
|
||||
var self = this,
|
||||
videoList = this.getVideoObjectsList();
|
||||
* Validates video list using provided validator.
|
||||
* @param {Function} validator Function that validate provided list.
|
||||
* @param {Object} list List of objects with information about the
|
||||
* values currently displayed in the editor.
|
||||
* @param {String} message Error message.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
checkIsValid: function (validator, list, message) {
|
||||
var videoList = list || this.getVideoObjectsList(),
|
||||
isValid = true;
|
||||
|
||||
if (!this.checkIsUniqVideoTypes(videoList)) {
|
||||
if (!validator(videoList)) {
|
||||
this.messenger.showError(message, true);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates if video types are unique.
|
||||
* @param {Object} list List of objects with information about the
|
||||
* values currently displayed in the editor.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
checkIsUniqVideoTypes: function (list) {
|
||||
return this.checkIsValid(
|
||||
this.isUniqVideoTypes, list, 'Link types should be unique.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates if other videos ids are unique.
|
||||
* editor/view have duplicate types.
|
||||
* @param {Object} list List of objects with information about the
|
||||
* values currently displayed in the editor.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
checkIsUniqOtherVideos: function (list) {
|
||||
return this.checkIsValid(
|
||||
this.isUniqOtherVideos, list, 'Links should be unique.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the values currently displayed in the editor/view have
|
||||
* valid values and show error messages.
|
||||
* @param {Object} data Objects with information about the value
|
||||
* currently displayed in the editor/view
|
||||
* @param {Boolean} showErrorModeMessage Disable mode validation
|
||||
* @return {Boolean} Boolean value that indicate if value is valid.
|
||||
*/
|
||||
checkValidity: function (data, showErrorModeMessage) {
|
||||
var videoList = this.getVideoObjectsList(),
|
||||
isUniqTypes = this.checkIsUniqVideoTypes.bind(this),
|
||||
isUniqOtherVideos = this.checkIsUniqOtherVideos.bind(this);
|
||||
|
||||
if (!isUniqTypes(videoList) || !isUniqOtherVideos(videoList)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.mode === 'incorrect' && showErrorModeMessage) {
|
||||
this.messenger
|
||||
.showError('Incorrect url format.', true);
|
||||
this.messenger.showError('Incorrect url format.', true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,53 +4,49 @@ return (function () {
|
||||
var Storage = {};
|
||||
|
||||
/**
|
||||
* Adds some data to the Storage object. If data with existent `data_id`
|
||||
* is added, nothing happens.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier for the data.
|
||||
* @param {Any} data Data that should be stored.
|
||||
* @return {Object} Object itself for chaining.
|
||||
*/
|
||||
* Adds some data to the Storage object. If data with existent `data_id`
|
||||
* is added, nothing happens.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier for the data.
|
||||
* @param {Any} data Data that should be stored.
|
||||
* @return {Object} Object itself for chaining.
|
||||
*/
|
||||
Storage.set = function (data_id, data) {
|
||||
Storage[data_id] = data;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return data from the Storage object by identifier.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier of the data.
|
||||
* @return {Any} Stored data.
|
||||
*/
|
||||
* Return data from the Storage object by identifier.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier of the data.
|
||||
* @return {Any} Stored data.
|
||||
*/
|
||||
Storage.get= function (data_id) {
|
||||
|
||||
return Storage[data_id];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deletes data from the Storage object by identifier.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier of the data.
|
||||
* @return {Boolean} Boolean value that indicate if data is removed.
|
||||
*/
|
||||
* Deletes data from the Storage object by identifier.
|
||||
* @function
|
||||
* @param {String} data_id Unique identifier of the data.
|
||||
* @return {Boolean} Boolean value that indicate if data is removed.
|
||||
*/
|
||||
Storage.remove = function (data_id) {
|
||||
|
||||
return (delete Storage[data_id]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns model from collection by 'field_name' property.
|
||||
* @function
|
||||
* @param {Object} collection The model (CMS.Models.Metadata) information
|
||||
* about metadata setting editors.
|
||||
* @param {String} field_name Name of field that should be found.
|
||||
* @return {
|
||||
* Object: When model exist.
|
||||
* Undefined: When model doesn't exist.
|
||||
* }
|
||||
*/
|
||||
* Returns model from collection by 'field_name' property.
|
||||
* @function
|
||||
* @param {Object} collection The model (CMS.Models.Metadata) information
|
||||
* about metadata setting editors.
|
||||
* @param {String} field_name Name of field that should be found.
|
||||
* @return {
|
||||
* Object: When model exist.
|
||||
* Undefined: When model doesn't exist.
|
||||
* }
|
||||
*/
|
||||
var _getField = function (collection, field_name) {
|
||||
var model;
|
||||
|
||||
@@ -64,30 +60,29 @@ return (function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses Youtube link and return video id.
|
||||
* @function
|
||||
* These are the types of URLs supported:
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM&feature=feedrec_grec_index
|
||||
* http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/OEoXaMPEzfM
|
||||
* http://www.youtube.com/v/OEoXaMPEzfM?fs=1&hl=en_US&rel=0
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM#t=0m10s
|
||||
* http://www.youtube.com/embed/OEoXaMPEzfM?rel=0
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM
|
||||
* http://youtu.be/OEoXaMPEzfM
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* String: Video Id.
|
||||
* Undefined: When url has incorrect format or argument is
|
||||
* non-string, video id's length is not equal 11.
|
||||
* }
|
||||
*/
|
||||
* Parses Youtube link and return video id.
|
||||
* @function
|
||||
* These are the types of URLs supported:
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM&feature=feedrec_grec_index
|
||||
* http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/OEoXaMPEzfM
|
||||
* http://www.youtube.com/v/OEoXaMPEzfM?fs=1&hl=en_US&rel=0
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM#t=0m10s
|
||||
* http://www.youtube.com/embed/OEoXaMPEzfM?rel=0
|
||||
* http://www.youtube.com/watch?v=OEoXaMPEzfM
|
||||
* http://youtu.be/OEoXaMPEzfM
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* String: Video Id.
|
||||
* Undefined: When url has incorrect format or argument is
|
||||
* non-string, video id's length is not equal 11.
|
||||
* }
|
||||
*/
|
||||
var _youtubeParser = (function () {
|
||||
var cache = {},
|
||||
regExp = /(?:http|https|)(?:\:\/\/|)(?:www.|)(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/ytscreeningroom\?v=|\/feeds\/api\/videos\/|\/user\S*[^\w\-\s]|\S*[^\w\-\s]))([\w\-]{11})[a-z0-9;:@#?&%=+\/\$_.-]*/i;
|
||||
regExp = /(?:http|https|)(?:\:\/\/|)(?:www.|)(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/ytscreeningroom\?v=|\/feeds\/api\/videos\/|\/user\S*[^\w\-\s]|\S*[^\w\-\s]))([\w\-]+)/i;
|
||||
|
||||
return function (url) {
|
||||
if (typeof url !== 'string') {
|
||||
|
||||
return void(0);
|
||||
}
|
||||
|
||||
@@ -96,25 +91,23 @@ return (function () {
|
||||
}
|
||||
|
||||
var match = url.match(regExp);
|
||||
cache[url] = (match && match[1].length === 11) ?
|
||||
match[1] :
|
||||
void(0);
|
||||
cache[url] = (match) ? match[1] : void(0);
|
||||
|
||||
return cache[url];
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Parses links with html5 video sources in mp4 or webm formats.
|
||||
* @function
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* object: Object with information about the video
|
||||
* (file name, video type),
|
||||
* undefined: when url has incorrect format or argument is
|
||||
* non-string.
|
||||
* }
|
||||
*/
|
||||
* Parses links with html5 video sources in mp4 or webm formats.
|
||||
* @function
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* Object: Object with information about the video
|
||||
* (file name, video type),
|
||||
* Undefined: when url has incorrect format or argument is
|
||||
* non-string.
|
||||
* }
|
||||
*/
|
||||
var _videoLinkParser = (function () {
|
||||
var cache = {};
|
||||
|
||||
@@ -132,57 +125,65 @@ return (function () {
|
||||
match;
|
||||
|
||||
link.href = url;
|
||||
match = link.pathname
|
||||
.split('/')
|
||||
.pop()
|
||||
.match(/(.+)\.(mp?4v?|webm)$/);
|
||||
|
||||
// The regular expression try catches file name and file extension.
|
||||
// '[scheme://hostname/pathname/]filename.extension[?query#hash]'
|
||||
match = link.pathname.match(/\/{1}([^\/]+)\.([^\/]+)$/);
|
||||
if (match) {
|
||||
cache[url] = {
|
||||
video: match[1],
|
||||
type: match[2]
|
||||
};
|
||||
} /*else {
|
||||
cache[url] = {
|
||||
video: link.pathname
|
||||
.split('/')
|
||||
.pop(),
|
||||
type: 'other'
|
||||
};
|
||||
}*/
|
||||
} else {
|
||||
// Links like http://goo.gl/pxxZrg
|
||||
// The regular expression try catches file name.
|
||||
// '[scheme://hostname/pathname/]filename[?query#hash]'
|
||||
match = link.pathname.match(/\/{1}([^\/\.]+)$/);
|
||||
if (match) {
|
||||
cache[url] = {
|
||||
video: match[1],
|
||||
type: 'other'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return cache[url];
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Facade function that parses html5 and youtube links.
|
||||
* @function
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* object: Object with information about the video:
|
||||
* {
|
||||
* mode: "youtube|html5|incorrect",
|
||||
* video: "file_name|youtube_id",
|
||||
* type: "youtube|mp4|webm"
|
||||
* },
|
||||
* undefined: when argument is non-string.
|
||||
* }
|
||||
*/
|
||||
* Facade function that parses html5 and youtube links.
|
||||
* @function
|
||||
* @param {String} url Url that should be parsed.
|
||||
* @return {
|
||||
* object: Object with information about the video:
|
||||
* {
|
||||
* mode: "youtube|html5|incorrect",
|
||||
* video: "file_name|youtube_id",
|
||||
* type: "youtube|mp4|webm|other"
|
||||
* },
|
||||
* undefined: when argument is non-string.
|
||||
* }
|
||||
*/
|
||||
var _linkParser = function (url) {
|
||||
var result;
|
||||
var youtubeIdLength = 11,
|
||||
result;
|
||||
|
||||
if (typeof url !== 'string') {
|
||||
|
||||
return void(0);
|
||||
}
|
||||
|
||||
if (_youtubeParser(url)) {
|
||||
result = {
|
||||
mode: 'youtube',
|
||||
video: _youtubeParser(url),
|
||||
type: 'youtube'
|
||||
};
|
||||
if (_youtubeParser(url).length === youtubeIdLength) {
|
||||
result = {
|
||||
mode: 'youtube',
|
||||
video: _youtubeParser(url),
|
||||
type: 'youtube'
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
mode: 'incorrect'
|
||||
};
|
||||
}
|
||||
} else if (_videoLinkParser(url)) {
|
||||
result = $.extend({mode: 'html5'}, _videoLinkParser(url));
|
||||
} else {
|
||||
@@ -195,37 +196,36 @@ return (function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns short-hand youtube url.
|
||||
* @function
|
||||
* @param {string} video_id Youtube Video Id that will be added to the
|
||||
* link.
|
||||
* @return {string} Short-hand Youtube url.
|
||||
* @examples
|
||||
* _getYoutubeLink('OEoXaMPEzfM'); => 'http://youtu.be/OEoXaMPEzfM'
|
||||
*/
|
||||
* Returns short-hand youtube url.
|
||||
* @function
|
||||
* @param {String} video_id Youtube Video Id that will be added to the link.
|
||||
* @return {String} Short-hand Youtube url.
|
||||
* @examples
|
||||
* _getYoutubeLink('OEoXaMPEzfM'); => 'http://youtu.be/OEoXaMPEzfM'
|
||||
*/
|
||||
var _getYoutubeLink = function (video_id) {
|
||||
return 'http://youtu.be/' + video_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns list of objects with information about the passed links.
|
||||
* @function
|
||||
* @param {array} links List of links that will be processed.
|
||||
* @returns {array} List of objects.
|
||||
* @examples
|
||||
* var links = [
|
||||
* 'http://youtu.be/OEoXaMPEzfM',
|
||||
* 'video_name.mp4',
|
||||
* 'video_name.webm'
|
||||
* ]
|
||||
*
|
||||
* _getVideoList(links); // =>
|
||||
* [
|
||||
* {mode: `youtube`, type: `youtube`, ...},
|
||||
* {mode: `html5`, type: `mp4`, ...},
|
||||
* {mode: `html5`, type: `webm`, ...}
|
||||
* ]
|
||||
*/
|
||||
* Returns list of objects with information about the passed links.
|
||||
* @function
|
||||
* @param {Array} links List of links that will be processed.
|
||||
* @returns {Array} List of objects.
|
||||
* @examples
|
||||
* var links = [
|
||||
* 'http://youtu.be/OEoXaMPEzfM',
|
||||
* 'video_name.mp4',
|
||||
* 'video_name.webm'
|
||||
* ]
|
||||
*
|
||||
* _getVideoList(links); // =>
|
||||
* [
|
||||
* {mode: `youtube`, type: `youtube`, ...},
|
||||
* {mode: `html5`, type: `mp4`, ...},
|
||||
* {mode: `html5`, type: `webm`, ...}
|
||||
* ]
|
||||
*/
|
||||
var _getVideoList = function (links) {
|
||||
if ($.isArray(links)) {
|
||||
var arr = [],
|
||||
@@ -243,14 +243,13 @@ return (function () {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Synchronizes 2 Backbone collections by 'field_name' property.
|
||||
* @function
|
||||
* @param {Object} fromCollection Collection with which synchronization will
|
||||
* happens.
|
||||
* @param {Object} toCollection Collection which will synchronized.
|
||||
*/
|
||||
* Synchronizes 2 Backbone collections by 'field_name' property.
|
||||
* @function
|
||||
* @param {Object} fromCollection Collection with which synchronization will
|
||||
* happens.
|
||||
* @param {Object} toCollection Collection which will synchronized.
|
||||
*/
|
||||
var _syncCollections = function (fromCollection, toCollection) {
|
||||
fromCollection.each(function (m) {
|
||||
var model = toCollection.findWhere({
|
||||
@@ -264,18 +263,18 @@ return (function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends Ajax requests in appropriate format.
|
||||
* @function
|
||||
* @param {String} action Action that will be invoked on server.
|
||||
* @param {String} component_locator the locator of component.
|
||||
* @param {Array} videoList List of object with information about inserted
|
||||
* urls.
|
||||
* @param {Object} extraParams Extra parameters that can be send to the
|
||||
* server.
|
||||
* @return {Object} XMLHttpRequest object. Using this object, we can
|
||||
* attach callbacks to AJAX request events (for example on 'done',
|
||||
* 'fail', etc.).
|
||||
*/
|
||||
* Sends Ajax requests in appropriate format.
|
||||
* @function
|
||||
* @param {String} action Action that will be invoked on server.
|
||||
* @param {String} component_locator the locator of component.
|
||||
* @param {Array} videoList List of object with information about inserted
|
||||
* urls.
|
||||
* @param {Object} extraParams Extra parameters that can be send to the
|
||||
* server.
|
||||
* @return {Object} XMLHttpRequest object. Using this object, we can
|
||||
* attach callbacks to AJAX request events (for example on 'done',
|
||||
* 'fail', etc.).
|
||||
*/
|
||||
var _command = (function () {
|
||||
// We will store the XMLHttpRequest object that $.ajax() function
|
||||
// returns, to abort an ongoing AJAX request (if necessary) upon
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
data-transcript-translation-url="/transcript/translation"
|
||||
data-transcript-available-translations-url="/transcript/available_translations"
|
||||
data-sub="Z5KLxerq05Y"
|
||||
data-mp4-source="xmodule/include/fixtures/test.mp4"
|
||||
data-webm-source="xmodule/include/fixtures/test.webm"
|
||||
data-ogg-source="xmodule/include/fixtures/test.ogv"
|
||||
data-sources='["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"]'
|
||||
data-autoplay="False"
|
||||
data-yt-test-timeout="1500"
|
||||
data-yt-api-url="www.youtube.com/iframe_api"
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
data-transcript-translation-url="/transcript/translation"
|
||||
data-transcript-available-translations-url="/transcript/available_translations"
|
||||
data-sub="Z5KLxerq05Y"
|
||||
data-mp4-source="xmodule/include/fixtures/test.mp4"
|
||||
data-webm-source="xmodule/include/fixtures/test.webm"
|
||||
data-ogg-source="xmodule/include/fixtures/test.ogv"
|
||||
data-sources='["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"]'
|
||||
data-autoplay="False"
|
||||
data-yt-test-timeout="1500"
|
||||
data-yt-api-url="www.youtube.com/iframe_api"
|
||||
|
||||
@@ -79,53 +79,6 @@
|
||||
expect(state.videos).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parse Html5 sources', function () {
|
||||
var html5Sources = {
|
||||
mp4: null,
|
||||
webm: null,
|
||||
ogg: null
|
||||
}, v = document.createElement('video');
|
||||
|
||||
if (
|
||||
!!(
|
||||
v.canPlayType &&
|
||||
v.canPlayType(
|
||||
'video/webm; codecs="vp8, vorbis"'
|
||||
).replace(/no/, '')
|
||||
)
|
||||
) {
|
||||
html5Sources['webm'] =
|
||||
'xmodule/include/fixtures/test.webm';
|
||||
}
|
||||
|
||||
if (
|
||||
!!(
|
||||
v.canPlayType &&
|
||||
v.canPlayType(
|
||||
'video/mp4; codecs="avc1.42E01E, ' +
|
||||
'mp4a.40.2"'
|
||||
).replace(/no/, '')
|
||||
)
|
||||
) {
|
||||
html5Sources['mp4'] =
|
||||
'xmodule/include/fixtures/test.mp4';
|
||||
}
|
||||
|
||||
if (
|
||||
!!(
|
||||
v.canPlayType &&
|
||||
v.canPlayType(
|
||||
'video/ogg; codecs="theora"'
|
||||
).replace(/no/, '')
|
||||
)
|
||||
) {
|
||||
html5Sources['ogg'] =
|
||||
'xmodule/include/fixtures/test.ogv';
|
||||
}
|
||||
|
||||
expect(state.html5Sources).toEqual(html5Sources);
|
||||
});
|
||||
|
||||
it('parse available video speeds', function () {
|
||||
var speeds = jasmine.stubbedHtml5Speeds;
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ function (VideoPlayer, VideoStorage, i18n) {
|
||||
isFlashMode: isFlashMode,
|
||||
isYoutubeType: isYoutubeType,
|
||||
parseSpeed: parseSpeed,
|
||||
parseVideoSources: parseVideoSources,
|
||||
parseYoutubeStreams: parseYoutubeStreams,
|
||||
saveState: saveState,
|
||||
setPlayerMode: setPlayerMode,
|
||||
@@ -280,32 +279,17 @@ function (VideoPlayer, VideoStorage, i18n) {
|
||||
// The function prepare HTML5 video, parse HTML5
|
||||
// video sources etc.
|
||||
function _prepareHTML5Video(state) {
|
||||
state.parseVideoSources(
|
||||
{
|
||||
mp4: state.config.mp4Source,
|
||||
webm: state.config.webmSource,
|
||||
ogg: state.config.oggSource
|
||||
}
|
||||
);
|
||||
|
||||
state.speeds = ['0.75', '1.0', '1.25', '1.50'];
|
||||
|
||||
// We must have at least one non-YouTube video source available.
|
||||
// Otherwise, return a negative.
|
||||
if (
|
||||
state.html5Sources.webm === null &&
|
||||
state.html5Sources.mp4 === null &&
|
||||
state.html5Sources.ogg === null
|
||||
) {
|
||||
|
||||
// TODO: use 1 class to work with.
|
||||
state.el.find('.video-player div').addClass('hidden');
|
||||
state.el.find('.video-player h3').removeClass('hidden');
|
||||
// If none of the supported video formats can be played and there is no
|
||||
// short-hand video links, than hide the spinner and show error message.
|
||||
if (!state.config.sources.length) {
|
||||
_hideWaitPlaceholder(state);
|
||||
|
||||
console.log(
|
||||
'[Video info]: Non-youtube video sources aren\'t available.'
|
||||
);
|
||||
state.el
|
||||
.find('.video-player div')
|
||||
.addClass('hidden')
|
||||
.end()
|
||||
.find('.video-player h3')
|
||||
.removeClass('hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -642,48 +626,6 @@ function (VideoPlayer, VideoStorage, i18n) {
|
||||
return _.isString(this.videos['1.0']);
|
||||
}
|
||||
|
||||
// function parseVideoSources(, mp4Source, webmSource, oggSource)
|
||||
//
|
||||
// Take the HTML5 sources (URLs of videos), and make them available
|
||||
// explictly for each type of video format (mp4, webm, ogg).
|
||||
function parseVideoSources(sources) {
|
||||
var _this = this,
|
||||
v = document.createElement('video'),
|
||||
sourceCodecs = {
|
||||
mp4: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
||||
webm: 'video/webm; codecs="vp8, vorbis"',
|
||||
ogg: 'video/ogg; codecs="theora"'
|
||||
};
|
||||
|
||||
this.html5Sources = {
|
||||
mp4: null,
|
||||
webm: null,
|
||||
ogg: null
|
||||
};
|
||||
|
||||
$.each(sources, function (name, source) {
|
||||
if (source && source.length) {
|
||||
if (
|
||||
Boolean(
|
||||
v.canPlayType &&
|
||||
v.canPlayType(sourceCodecs[name]).replace(/no/, '')
|
||||
)
|
||||
) {
|
||||
_this.html5Sources[name] = source;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// None of the supported video formats can be played. Hide the spinner.
|
||||
if (!(_.compact(_.values(this.html5Sources)))) {
|
||||
_hideWaitPlaceholder(state);
|
||||
console.log(
|
||||
'[Video info]: This browser cannot play .mp4, .ogg, or .webm ' +
|
||||
'files'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// function fetchMetadata()
|
||||
//
|
||||
// When dealing with YouTube videos, we must fetch meta data that has
|
||||
@@ -763,7 +705,10 @@ function (VideoPlayer, VideoStorage, i18n) {
|
||||
}
|
||||
successHandler = ($.isFunction(callback)) ? callback : null;
|
||||
xhr = $.ajax({
|
||||
url: document.location.protocol + '//' + this.config.ytTestUrl + url + '?v=2&alt=jsonc',
|
||||
url: [
|
||||
document.location.protocol, '//', this.config.ytTestUrl, url,
|
||||
'?v=2&alt=jsonc'
|
||||
].join(''),
|
||||
dataType: 'jsonp',
|
||||
timeout: this.config.ytTestTimeout,
|
||||
success: successHandler
|
||||
|
||||
@@ -94,6 +94,22 @@ function () {
|
||||
return this.logs;
|
||||
};
|
||||
|
||||
Player.prototype.showErrorMessage = function () {
|
||||
this.el
|
||||
.find('.video-player div')
|
||||
.addClass('hidden')
|
||||
.end()
|
||||
.find('.video-player h3')
|
||||
.removeClass('hidden')
|
||||
.end()
|
||||
.addClass('is-initialized')
|
||||
.find('.spinner')
|
||||
.attr({
|
||||
'aria-hidden': 'true',
|
||||
'tabindex': -1
|
||||
});
|
||||
};
|
||||
|
||||
return Player;
|
||||
|
||||
/*
|
||||
@@ -113,7 +129,7 @@ function () {
|
||||
*
|
||||
* config = {
|
||||
*
|
||||
* videoSources: {}, // An object with properties being video
|
||||
* videoSources: [], // An array with properties being video
|
||||
* // sources. The property name is the
|
||||
* // video format of the source. Supported
|
||||
* // video formats are: 'mp4', 'webm', and
|
||||
@@ -134,7 +150,7 @@ function () {
|
||||
*/
|
||||
function Player(el, config) {
|
||||
var isTouch = onTouchBasedDevice() || '',
|
||||
sourceStr, _this, errorMessage;
|
||||
sourceList, _this, errorMessage, lastSource;
|
||||
|
||||
this.logs = [];
|
||||
// Initially we assume that el is a DOM element. If jQuery selector
|
||||
@@ -167,63 +183,50 @@ function () {
|
||||
|
||||
// We should have at least one video source. Otherwise there is no
|
||||
// point to continue.
|
||||
if (!config.videoSources) {
|
||||
if (!config.videoSources && !config.videoSources.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From the start, all sources are empty. We will populate this
|
||||
// object below.
|
||||
sourceStr = {
|
||||
mp4: ' ',
|
||||
webm: ' ',
|
||||
ogg: ' '
|
||||
};
|
||||
|
||||
// Will be used in inner functions to point to the current object.
|
||||
_this = this;
|
||||
|
||||
// Create HTML markup for individual sources of the HTML5 <video>
|
||||
// element.
|
||||
$.each(sourceStr, function (videoType, videoSource) {
|
||||
var url = _this.config.videoSources[videoType];
|
||||
if (url && url.length) {
|
||||
sourceStr[videoType] =
|
||||
'<source ' +
|
||||
'src="' + url +
|
||||
// Following hack allows to open the same video twice
|
||||
// https://code.google.com/p/chromium/issues/detail?id=31014
|
||||
// Check whether the url already has a '?' inside, and if so,
|
||||
// use '&' instead of '?' to prevent breaking the url's integrity.
|
||||
(url.indexOf('?') == -1 ? '?' : '&') + (new Date()).getTime() +
|
||||
'" ' + 'type="video/' + videoType + '" ' +
|
||||
'/> ';
|
||||
}
|
||||
sourceList = $.map(config.videoSources, function (source) {
|
||||
return [
|
||||
'<source ',
|
||||
'src="', source,
|
||||
// Following hack allows to open the same video twice
|
||||
// https://code.google.com/p/chromium/issues/detail?id=31014
|
||||
// Check whether the url already has a '?' inside, and if so,
|
||||
// use '&' instead of '?' to prevent breaking the url's integrity.
|
||||
(source.indexOf('?') === -1 ? '?' : '&'),
|
||||
(new Date()).getTime(), '" />'
|
||||
].join('');
|
||||
});
|
||||
|
||||
// We should have at least one video source. Otherwise there is no
|
||||
// point to continue.
|
||||
if (
|
||||
sourceStr.mp4 === ' ' &&
|
||||
sourceStr.webm === ' ' &&
|
||||
sourceStr.ogg === ' '
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create HTML markup for the <video> element, populating it with
|
||||
// sources from previous step. Because of problems with creating
|
||||
// video element via jquery (http://bugs.jquery.com/ticket/9174) we
|
||||
// create it using native JS.
|
||||
this.video = document.createElement('video');
|
||||
errorMessage = gettext('This browser cannot play .mp4, .ogg, or .webm files.')
|
||||
+ gettext('Try using a different browser, such as Google Chrome.');
|
||||
this.video.innerHTML = _.values(sourceStr).join('') + errorMessage;
|
||||
|
||||
errorMessage = [
|
||||
gettext('This browser cannot play .mp4, .ogg, or .webm files.'),
|
||||
gettext('Try using a different browser, such as Google Chrome.')
|
||||
].join('');
|
||||
this.video.innerHTML = sourceList.join('') + errorMessage;
|
||||
|
||||
// Get the jQuery object, and set the player state to UNSTARTED.
|
||||
// The player state is used by other parts of the VideoPlayer to
|
||||
// determine what the video is currently doing.
|
||||
this.videoEl = $(this.video);
|
||||
|
||||
lastSource = this.videoEl.find('source').last();
|
||||
lastSource.on('error', this.showErrorMessage.bind(this));
|
||||
|
||||
if (/iP(hone|od)/i.test(isTouch[0])) {
|
||||
this.videoEl.prop('controls', true);
|
||||
}
|
||||
@@ -253,6 +256,7 @@ function () {
|
||||
'durationchange', 'volumechange'
|
||||
];
|
||||
|
||||
this.debug = false;
|
||||
$.each(events, function(index, eventName) {
|
||||
_this.video.addEventListener(eventName, function () {
|
||||
_this.logs.push({
|
||||
@@ -260,6 +264,15 @@ function () {
|
||||
'state': _this.playerState
|
||||
});
|
||||
|
||||
if (_this.debug) {
|
||||
console.log(
|
||||
'event name:', eventName,
|
||||
'state:', _this.playerState,
|
||||
'readyState:', _this.video.readyState,
|
||||
'networkState:', _this.video.networkState
|
||||
);
|
||||
}
|
||||
|
||||
el.trigger('html5:' + eventName, arguments);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,7 +142,7 @@ function (HTML5Video, Resizer) {
|
||||
if (state.videoType === 'html5') {
|
||||
state.videoPlayer.player = new HTML5Video.Player(state.el, {
|
||||
playerVars: state.videoPlayer.playerVars,
|
||||
videoSources: state.html5Sources,
|
||||
videoSources: state.config.sources,
|
||||
events: {
|
||||
onReady: state.videoPlayer.onReady,
|
||||
onStateChange: state.videoPlayer.onStateChange
|
||||
|
||||
@@ -20,7 +20,7 @@ from mock import Mock
|
||||
from . import LogicTest
|
||||
from lxml import etree
|
||||
from opaque_keys.edx.locations import Location
|
||||
from xmodule.video_module import VideoDescriptor, create_youtube_string, get_ext
|
||||
from xmodule.video_module import VideoDescriptor, create_youtube_string
|
||||
from .test_import import DummySystem
|
||||
from xblock.field_data import DictFieldData
|
||||
from xblock.fields import ScopeIds
|
||||
@@ -107,18 +107,6 @@ class VideoModuleTest(LogicTest):
|
||||
'1.50': ''}
|
||||
)
|
||||
|
||||
def test_get_ext(self):
|
||||
"""Test get the file's extension in a url without query string."""
|
||||
filename_str = 'http://www.example.com/path/video.mp4'
|
||||
output = get_ext(filename_str)
|
||||
self.assertEqual(output, 'mp4')
|
||||
|
||||
def test_get_ext_with_query_string(self):
|
||||
"""Test get the file's extension in a url with query string."""
|
||||
filename_str = 'http://www.example.com/path/video.mp4?param1=1&p2=2'
|
||||
output = get_ext(filename_str)
|
||||
self.assertEqual(output, 'mp4')
|
||||
|
||||
|
||||
class VideoDescriptorTest(unittest.TestCase):
|
||||
"""Test for VideoDescriptor"""
|
||||
|
||||
@@ -35,14 +35,6 @@ from .video_utils import create_youtube_string
|
||||
from .video_xfields import VideoFields
|
||||
from .video_handlers import VideoStudentViewHandlers, VideoStudioViewHandlers
|
||||
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
def get_ext(filename):
|
||||
# Prevent incorrectly parsing urls like 'http://abc.com/path/video.mp4?xxxx'.
|
||||
path = urlparse(filename).path
|
||||
return path.rpartition('.')[-1]
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_ = lambda text: text
|
||||
@@ -97,15 +89,15 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
|
||||
|
||||
def get_html(self):
|
||||
track_url = None
|
||||
download_video_link = None
|
||||
transcript_download_format = self.transcript_download_format
|
||||
|
||||
sources = {get_ext(src): src for src in self.html5_sources}
|
||||
sources = filter(None, self.html5_sources)
|
||||
|
||||
if self.download_video:
|
||||
if self.source:
|
||||
sources['main'] = self.source
|
||||
download_video_link = self.source
|
||||
elif self.html5_sources:
|
||||
sources['main'] = self.html5_sources[0]
|
||||
download_video_link = self.html5_sources[0]
|
||||
|
||||
if self.download_track:
|
||||
if self.track:
|
||||
@@ -149,7 +141,8 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
|
||||
'handout': self.handout,
|
||||
'id': self.location.html_id(),
|
||||
'show_captions': json.dumps(self.show_captions),
|
||||
'sources': sources,
|
||||
'download_video_link': download_video_link,
|
||||
'sources': json.dumps(sources),
|
||||
'speed': json.dumps(self.speed),
|
||||
'general_speed': self.global_speed,
|
||||
'saved_video_position': self.saved_video_position.total_seconds(),
|
||||
|
||||
@@ -26,12 +26,7 @@ class TestVideoYouTube(TestVideo):
|
||||
def test_video_constructor(self):
|
||||
"""Make sure that all parameters extracted correctly from xml"""
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
|
||||
sources = {
|
||||
'main': u'example.mp4',
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm',
|
||||
}
|
||||
sources = json.dumps([u'example.mp4', u'example.webm'])
|
||||
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
@@ -42,6 +37,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
'download_video_link': u'example.mp4',
|
||||
'sources': sources,
|
||||
'speed': 'null',
|
||||
'general_speed': 1.0,
|
||||
@@ -56,7 +52,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'transcript_download_format': 'srt',
|
||||
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
|
||||
'transcript_language': u'en',
|
||||
'transcript_languages': json.dumps(OrderedDict({"en": "English", "uk": u"Українська"})),
|
||||
'transcript_languages': json.dumps(OrderedDict({"en": "English", "uk": u"Українська"})),
|
||||
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'translation'
|
||||
).rstrip('/?'),
|
||||
@@ -93,13 +89,8 @@ class TestVideoNonYouTube(TestVideo):
|
||||
"""Make sure that if the 'youtube' attribute is omitted in XML, then
|
||||
the template generates an empty string for the YouTube streams.
|
||||
"""
|
||||
sources = {
|
||||
'main': u'example.mp4',
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm',
|
||||
}
|
||||
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
sources = json.dumps([u'example.mp4', u'example.webm'])
|
||||
|
||||
expected_context = {
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
@@ -107,6 +98,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
'display_name': u'A Name',
|
||||
'download_video_link': u'example.mp4',
|
||||
'end': 3610.0,
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'sources': sources,
|
||||
@@ -148,7 +140,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
METADATA = {}
|
||||
|
||||
def setUp(self):
|
||||
self.setup_course();
|
||||
self.setup_course()
|
||||
|
||||
def test_get_html_track(self):
|
||||
SOURCE_XML = """
|
||||
@@ -201,19 +193,17 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcripts': '<transcript language="uk" src="ukrainian.srt" />',
|
||||
},
|
||||
]
|
||||
sources = json.dumps([u'example.mp4', u'example.webm'])
|
||||
|
||||
expected_context = {
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
'display_name': u'A Name',
|
||||
'download_video_link': u'example.mp4',
|
||||
'end': 3610.0,
|
||||
'id': None,
|
||||
'sources': {
|
||||
'main': u'example.mp4',
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm'
|
||||
},
|
||||
'sources': sources,
|
||||
'start': 3603.0,
|
||||
'saved_video_position': 0.0,
|
||||
'sub': u'a_sub_file.srt.sjson',
|
||||
@@ -284,9 +274,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="example.webm"/>
|
||||
""",
|
||||
'result': {
|
||||
'main': u'example_source.mp4',
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm',
|
||||
'download_video_link': u'example_source.mp4',
|
||||
'sources': json.dumps([u'example.mp4', u'example.webm']),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -297,9 +286,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="example.webm"/>
|
||||
""",
|
||||
'result': {
|
||||
'main': u'example.mp4',
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm',
|
||||
'download_video_link': u'example.mp4',
|
||||
'sources': json.dumps([u'example.mp4', u'example.webm']),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -318,20 +306,20 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="example.webm"/>
|
||||
""",
|
||||
'result': {
|
||||
u'mp4': u'example.mp4',
|
||||
u'webm': u'example.webm',
|
||||
'sources': json.dumps([u'example.mp4', u'example.webm']),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
expected_context = {
|
||||
initial_context = {
|
||||
'data_dir': getattr(self, 'data_dir', None),
|
||||
'show_captions': 'true',
|
||||
'handout': None,
|
||||
'display_name': u'A Name',
|
||||
'download_video_link': None,
|
||||
'end': 3610.0,
|
||||
'id': None,
|
||||
'sources': None,
|
||||
'sources': '[]',
|
||||
'speed': 'null',
|
||||
'general_speed': 1.0,
|
||||
'start': 3603.0,
|
||||
@@ -358,6 +346,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
self.initialize_module(data=DATA)
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
|
||||
expected_context = dict(initial_context)
|
||||
expected_context.update({
|
||||
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'translation'
|
||||
@@ -366,9 +355,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
self.item_descriptor, 'transcript', 'available_translations'
|
||||
).rstrip('/?'),
|
||||
'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'sources': data['result'],
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
})
|
||||
expected_context.update(data['result'])
|
||||
|
||||
self.assertEqual(
|
||||
context,
|
||||
@@ -385,7 +374,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
METADATA = {}
|
||||
|
||||
def setUp(self):
|
||||
self.setup_course();
|
||||
self.setup_course()
|
||||
|
||||
def test_source_not_in_html5sources(self):
|
||||
metadata = {
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
${'data-sub="{}"'.format(sub) if sub else ''}
|
||||
${'data-autoplay="{}"'.format(autoplay) if autoplay else ''}
|
||||
|
||||
${'data-mp4-source="{}"'.format(sources.get('mp4')) if sources.get('mp4') else ''}
|
||||
${'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-sources='${sources}'
|
||||
data-save-state-url="${ajax_url}"
|
||||
data-caption-data-dir="${data_dir}"
|
||||
data-show-captions="${show_captions}"
|
||||
@@ -106,9 +103,9 @@
|
||||
|
||||
<div class="focus_grabber last"></div>
|
||||
<ul class="wrapper-downloads">
|
||||
% if sources.get('main'):
|
||||
% if download_video_link:
|
||||
<li class="video-sources video-download-button">
|
||||
${('<a href="%s">' + _('Download video') + '</a>') % sources.get('main')}
|
||||
${('<a href="%s">' + _('Download video') + '</a>') % download_video_link}
|
||||
</li>
|
||||
% endif
|
||||
% if track:
|
||||
|
||||
Reference in New Issue
Block a user