diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 75132000c1..79e7449c02 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 Youtube regular expression in video player editor. BLD-967. + Blades: Fix displaying transcripts on touch devices. BLD-1033. Blades: Tolerance expressed in percentage now computes correctly. BLD-522. diff --git a/cms/static/js/spec/video/transcripts/utils_spec.js b/cms/static/js/spec/video/transcripts/utils_spec.js index 0ac455d46e..68e09cdb4c 100644 --- a/cms/static/js/spec/video/transcripts/utils_spec.js +++ b/cms/static/js/spec/video/transcripts/utils_spec.js @@ -1,264 +1,266 @@ define( [ - "jquery", "underscore", - "js/views/video/transcripts/utils", - "underscore.string", "xmodule", "jasmine-jquery" + 'jquery', 'underscore', + 'js/views/video/transcripts/utils', + 'underscore.string', 'xmodule', 'jasmine-jquery' ], function ($, _, Utils, _str) { - describe('Transcripts.Utils', function () { - var videoId = 'OEoXaMPEzfM', - ytLinksList = (function (id) { - var links = [ - 'http://www.youtube.com/watch?v=%s&feature=feedrec_grec_index', - 'http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/%s', - 'http://www.youtube.com/v/%s?fs=1&hl=en_US&rel=0', - 'http://www.youtube.com/watch?v=%s#t=0m10s', - 'http://www.youtube.com/embed/%s?rel=0', - 'http://www.youtube.com/watch?v=%s', - 'http://youtu.be/%s' - ]; - - return $.map(links, function (link) { - return _str.sprintf(link, id); - }); - - } (videoId)), - html5FileName = 'file_name', - html5LinksList = (function (videoName) { - var videoTypes = ['mp4', 'webm'], - links = [ - 'http://somelink.com/%s.%s?param=1¶m=2#hash', - 'http://somelink.com/%s.%s#hash', - 'http://somelink.com/%s.%s?param=1¶m=2', - 'http://somelink.com/%s.%s', - 'ftp://somelink.com/%s.%s', - 'https://somelink.com/%s.%s', - 'somelink.com/%s.%s', - '%s.%s' - ], - data = {}; - - $.each(videoTypes, function (index, type) { - data[type] = $.map(links, function (link) { - return _str.sprintf(link, videoName, type); - }); - }); - - return data; - - } (html5FileName)); - - describe('Method: getField', function (){ - var collection, - testFieldName = 'test_field'; - - beforeEach(function() { - collection = jasmine.createSpyObj( - 'Collection', - [ - 'findWhere' - ] - ); - }); - - it('All works okay if all arguments are passed', function () { - Utils.getField(collection, testFieldName); - - expect(collection.findWhere).toHaveBeenCalledWith({ - field_name: testFieldName - }); - }); - - var wrongArgumentLists = [ - { - argName: 'collection', - list: [undefined, testFieldName] - }, - { - argName: 'field name', - list: [collection, undefined] - }, - { - argName: 'both', - list: [undefined, undefined] - } +'use strict'; +describe('Transcripts.Utils', function () { + var videoId = 'OEoXaMPEzfM', + ytLinksList = (function (id) { + var links = [ + 'http://www.youtube.com/watch?v=%s&feature=feedrec_grec_index', + 'http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/%s', + 'http://www.youtube.com/v/%s?fs=1&hl=en_US&rel=0', + 'http://www.youtube.com/watch?v=%s#t=0m10s', + 'http://www.youtube.com/embed/%s?rel=0', + 'http://www.youtube.com/watch?v=%s', + 'http://youtu.be/%s' ]; - $.each(wrongArgumentLists, function (index, element) { - it(element.argName + ' argument(s) is/are absent', function () { - var result = Utils.getField.apply(this, element.list); + return $.map(links, function (link) { + return _str.sprintf(link, id); + }); - expect(result).toBeUndefined(); + } (videoId)), + html5FileName = 'file_name', + html5LinksList = (function (videoName) { + var videoTypes = ['mp4', 'webm'], + links = [ + 'http://somelink.com/%s.%s?param=1¶m=2#hash', + 'http://somelink.com/%s.%s#hash', + 'http://somelink.com/%s.%s?param=1¶m=2', + 'http://somelink.com/%s.%s', + 'ftp://somelink.com/%s.%s', + 'https://somelink.com/%s.%s', + 'http://cdn.somecdn.net/v/%s.%s', + 'somelink.com/%s.%s', + '%s.%s' + ], + data = {}; + + $.each(videoTypes, function (index, type) { + data[type] = $.map(links, function (link) { + return _str.sprintf(link, videoName, type); + }); + }); + + return data; + + } (html5FileName)); + + describe('Method: getField', function (){ + var collection, + testFieldName = 'test_field'; + + beforeEach(function() { + collection = jasmine.createSpyObj( + 'Collection', + [ + 'findWhere' + ] + ); + }); + + it('All works okay if all arguments are passed', function () { + Utils.getField(collection, testFieldName); + + expect(collection.findWhere).toHaveBeenCalledWith({ + field_name: testFieldName + }); + }); + + var wrongArgumentLists = [ + { + argName: 'collection', + list: [undefined, testFieldName] + }, + { + argName: 'field name', + list: [collection, undefined] + }, + { + argName: 'both', + list: [undefined, undefined] + } + ]; + + $.each(wrongArgumentLists, function (index, element) { + it(element.argName + ' argument(s) is/are absent', function () { + var result = Utils.getField.apply(this, element.list); + + expect(result).toBeUndefined(); + }); + }); + }); + + describe('Method: parseYoutubeLink', function () { + describe('Supported urls', function () { + $.each(ytLinksList, function (index, link) { + it(link, function () { + var result = Utils.parseYoutubeLink(link); + + expect(result).toBe(videoId); }); }); }); - describe('Method: parseYoutubeLink', function () { - describe('Supported urls', function () { - $.each(ytLinksList, function (index, link) { - it(link, function () { - var result = Utils.parseYoutubeLink(link); + describe('Wrong arguments ', function () { - expect(result).toBe(videoId); - }); - }); + beforeEach(function(){ + spyOn(console, 'log'); }); - describe('Wrong arguments ', function () { + it('no arguments', function () { + var result = Utils.parseYoutubeLink(); - beforeEach(function(){ - spyOn(console, 'log'); - }); - - it('no arguments', function () { - var result = Utils.parseYoutubeLink(); - - expect(result).toBeUndefined(); - }); - - it('wrong data type', function () { - var result = Utils.parseYoutubeLink(1); - - 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', - 'http://google.com/somevideo.mp4' - ]; - - $.each(wrongUrls, function (index, link) { - it(link, function () { - var result = Utils.parseYoutubeLink(link); - - expect(result).toBeUndefined(); - }); - }); - }); - }); - - describe('Method: parseHTML5Link', function () { - describe('Supported urls', function () { - $.each(html5LinksList, function (format, linksList) { - $.each(linksList, function (index, link) { - it(link, function () { - var result = Utils.parseHTML5Link(link); - - expect(result).toEqual({ - video: html5FileName, - type: format - }); - }); - }); - }); + expect(result).toBeUndefined(); }); - describe('Wrong arguments ', function () { + it('wrong data type', function () { + var result = Utils.parseYoutubeLink(1); - beforeEach(function(){ - spyOn(console, 'log'); - }); - - it('no arguments', function () { - var result = Utils.parseHTML5Link(); - - expect(result).toBeUndefined(); - }); - - it('wrong data type', function () { - var result = Utils.parseHTML5Link(1); - - expect(result).toBeUndefined(); - }); - - 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_' - ]; - - $.each(html5WrongUrls, function (index, link) { - it(link, function () { - var result = Utils.parseHTML5Link(link); - - expect(result).toBeUndefined(); - }); - }); - }); - }); - - it('Method: getYoutubeLink', function () { - var videoId = 'video_id', - result = Utils.getYoutubeLink(videoId), - expectedResult = 'http://youtu.be/' + videoId; - - expect(result).toBe(expectedResult); - }); - - describe('Method: parseLink', function () { - var resultDataDict = { - 'html5': { - link: html5LinksList['mp4'][0], - resp: { - mode: 'html5', - video: html5FileName, - type: 'mp4' - } - }, - 'youtube': { - link: ytLinksList[0], - resp: { - mode: 'youtube', - video: videoId, - type: 'youtube' - } - }, - 'incorrect': { - link: 'http://example.com', - resp: { - mode: 'incorrect' - } - } - }; - - $.each(resultDataDict, function (mode, data) { - it(mode, function () { - var result = Utils.parseLink(data.link); - - expect(result).toEqual(data.resp); - }); + expect(result).toBeUndefined(); }); - describe('Wrong arguments ', function () { + it('videoId is wrong', function () { + var videoId = 'wrong_id', + link = 'http://youtu.be/' + videoId, + result = Utils.parseYoutubeLink(link); - it('no arguments', function () { - var result = Utils.parseLink(); + expect(result).toBeUndefined(); + }); - expect(result).toBeUndefined(); - }); + var wrongUrls = [ + 'http://youtu.bee/' + videoId, + 'http://youtu.be/', + 'example.com', + 'http://google.com/somevideo.mp4' + ]; - it('wrong data type', function () { - var result = Utils.parseLink(1); + $.each(wrongUrls, function (index, link) { + it(link, function () { + var result = Utils.parseYoutubeLink(link); expect(result).toBeUndefined(); }); }); }); }); + + describe('Method: parseHTML5Link', function () { + describe('Supported urls', function () { + $.each(html5LinksList, function (format, linksList) { + $.each(linksList, function (index, link) { + it(link, function () { + var result = Utils.parseHTML5Link(link); + + expect(result).toEqual({ + video: html5FileName, + type: format + }); + }); + }); + }); + }); + + describe('Wrong arguments ', function () { + + beforeEach(function(){ + spyOn(console, 'log'); + }); + + it('no arguments', function () { + var result = Utils.parseHTML5Link(); + + expect(result).toBeUndefined(); + }); + + it('wrong data type', function () { + var result = Utils.parseHTML5Link(1); + + expect(result).toBeUndefined(); + }); + + 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_' + ]; + + $.each(html5WrongUrls, function (index, link) { + it(link, function () { + var result = Utils.parseHTML5Link(link); + + expect(result).toBeUndefined(); + }); + }); + }); + }); + + it('Method: getYoutubeLink', function () { + var videoId = 'video_id', + result = Utils.getYoutubeLink(videoId), + expectedResult = 'http://youtu.be/' + videoId; + + expect(result).toBe(expectedResult); + }); + + describe('Method: parseLink', function () { + var resultDataDict = { + 'html5': { + link: html5LinksList.mp4[0], + resp: { + mode: 'html5', + video: html5FileName, + type: 'mp4' + } + }, + 'youtube': { + link: ytLinksList[0], + resp: { + mode: 'youtube', + video: videoId, + type: 'youtube' + } + }, + 'incorrect': { + link: 'http://example.com', + resp: { + mode: 'incorrect' + } + } + }; + + $.each(resultDataDict, function (mode, data) { + it(mode, function () { + var result = Utils.parseLink(data.link); + + expect(result).toEqual(data.resp); + }); + }); + + describe('Wrong arguments ', function () { + + it('no arguments', function () { + var result = Utils.parseLink(); + + expect(result).toBeUndefined(); + }); + + it('wrong data type', function () { + var result = Utils.parseLink(1); + + expect(result).toBeUndefined(); + }); + }); + }); +}); }); diff --git a/cms/static/js/views/video/transcripts/utils.js b/cms/static/js/views/video/transcripts/utils.js index a6865906e5..bbdb990c51 100644 --- a/cms/static/js/views/video/transcripts/utils.js +++ b/cms/static/js/views/video/transcripts/utils.js @@ -1,363 +1,332 @@ -define(["jquery", "underscore", "jquery.ajaxQueue"], function($, _) { - var Utils = (function () { - var Storage = {}; +define(['jquery', 'underscore', 'jquery.ajaxQueue'], function($) { +'use strict'; +return (function () { + var Storage = {}; - /** - * @function - * - * Adds some data to the Storage object. If data with existent `data_id` - * is added, nothing happens. - * - * @param {string} data_id Unique identifier for the data. - * @param {any} data Data that should be stored. - * - * @returns {object} Object itself for chaining. - */ - Storage.set = function (data_id, data) { - Storage[data_id] = data; + /** + * 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 this; + }; - /** - * @function - * - * Return data from the Storage object by identifier. - * - * @param {string} data_id Unique identifier of the data. - * - * @returns {any} Stored data. - */ - Storage.get= function (data_id) { + /** + * 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]; - }; + return Storage[data_id]; + }; - /** - * @function - * - * Deletes data from the Storage object by identifier. - * - * @param {string} data_id Unique identifier of the data. - * - * @returns {boolean} Boolean value that indicate if data is removed. - */ - Storage.remove = function (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. + */ + Storage.remove = function (data_id) { - return (delete Storage[data_id]); - }; + return (delete Storage[data_id]); + }; - /** - * @function - * - * Returns model from collection by 'field_name' property. - * - * @param {object} collection The model (CMS.Models.Metadata) containing - * information about metadata setting editors. - * @param {string} field_name Name of field that should be found. - * - * @returns { - * object: when model exist, - * undefined: when model doesn't exist. - * } - */ - var _getField = function (collection, field_name) { - var model; + /** + * 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; - if (collection && field_name) { - model = collection.findWhere({ - field_name: field_name - }); - } + if (collection && field_name) { + model = collection.findWhere({ + field_name: field_name + }); + } - return model; - }; + return model; + }; - /** - * @function - * - * Parses Youtube link and return video id. - * - * 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. - * - * @returns { - * 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 = {}; - - return function (url) { - if (typeof url !== 'string') { - - return void(0); - } - - if (cache[url]) { - return cache[url]; - } - - var regExp = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/; - var match = url.match(regExp); - cache[url] = (match && match[1].length === 11) ? match[1] : void(0); - - return cache[url]; - }; - }()); - - /** - * @function - * - * Parses links with html5 video sources in mp4 or webm formats. - * - * @param {string} url Url that should be parsed. - * - * @returns { - * 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 = {}; - - return function (url) { - if (typeof url !== 'string') { - - return void(0); - } - - if (cache[url]) { - return cache[url]; - } - - var link = document.createElement('a'), - match; - - link.href = url; - match = link.pathname - .split('/') - .pop() - .match(/(.+)\.(mp4|webm)$/); - - if (match) { - cache[url] = { - video: match[1], - type: match[2] - }; - } - - return cache[url]; - }; - }()); - - /** - * @function - * - * Facade function that parses html5 and youtube links. - * - * @param {string} url Url that should be parsed. - * - * @returns { - * 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. - * } - */ - var _linkParser = function (url) { - var result; + /** + * 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; + return function (url) { if (typeof url !== 'string') { return void(0); } - if (_youtubeParser(url)) { - result = { - mode: 'youtube', - video: _youtubeParser(url), - type: 'youtube' - }; - } else if (_videoLinkParser(url)) { - result = $.extend({mode: 'html5'}, _videoLinkParser(url)); - } else { - result = { - mode: 'incorrect' - }; + if (cache[url]) { + return cache[url]; } - return result; - }; + var match = url.match(regExp); + cache[url] = (match && match[1].length === 11) ? + match[1] : + void(0); - /** - * @function - * - * Returns short-hand youtube url. - * - * @param {string} video_id Youtube Video Id that will be added to the link. - * - * @returns {string} Short-hand Youtube url. - * - * @example - * _getYoutubeLink('OEoXaMPEzfM'); => 'http://youtu.be/OEoXaMPEzfM' - */ - var _getYoutubeLink = function (video_id) { - return 'http://youtu.be/' + video_id; - }; - - - - /** - * @function - * - * Returns list of objects with information about the passed links. - * - * @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 = [], - data; - - for (var i = 0, len = links.length; i < len; i += 1) { - data = _linkParser(links[i]); - - if (data.mode !== 'incorrect') { - arr.push(data); - } - } - - return arr; - } - }; - - - /** - * @function - * - * Synchronizes 2 Backbone collections by 'field_name' property. - * - * @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({ - field_name: m.getFieldName() - }); - - if (model) { - model.setValue(m.getDisplayValue()); - } - }); - }; - - /** - * @function - * - * Sends Ajax requests in appropriate format. - * - * @param {string} action Action that will be invoked on server. Is a part - * of url. - * @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 - * - * @returns {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 - // subsequent invocations of _command() function. - // - // A new AJAX request will be made on each invocation of the - // _command() function. - var xhr = null; - - return function (action, component_locator, videoList, extraParams) { - var params, data; - - if (extraParams) { - if ($.isPlainObject(extraParams)) { - params = extraParams; - } else { - params = {params: extraParams}; - } - } - - data = $.extend( - { locator: component_locator }, - { videos: videoList }, - params - ); - - xhr = $.ajaxQueue({ - url: '/transcripts/' + action, - data: { data: JSON.stringify(data) }, - notifyOnError: false, - type: 'get' - }); - - return xhr; - }; - }()); - - return { - getField: _getField, - parseYoutubeLink: _youtubeParser, - parseHTML5Link: _videoLinkParser, - parseLink: _linkParser, - getYoutubeLink: _getYoutubeLink, - syncCollections: _syncCollections, - command: _command, - getVideoList: _getVideoList, - Storage: { - set: Storage.set, - get: Storage.get, - remove: Storage.remove - } + return cache[url]; }; }()); - return Utils; + /** + * 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 = {}; + + return function (url) { + if (typeof url !== 'string') { + + return void(0); + } + + if (cache[url]) { + return cache[url]; + } + + var link = document.createElement('a'), + match; + + link.href = url; + match = link.pathname + .split('/') + .pop() + .match(/(.+)\.(mp?4v?|webm)$/); + + if (match) { + cache[url] = { + video: match[1], + type: match[2] + }; + } /*else { + cache[url] = { + video: link.pathname + .split('/') + .pop(), + 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. + * } + */ + var _linkParser = function (url) { + var result; + + if (typeof url !== 'string') { + + return void(0); + } + + if (_youtubeParser(url)) { + result = { + mode: 'youtube', + video: _youtubeParser(url), + type: 'youtube' + }; + } else if (_videoLinkParser(url)) { + result = $.extend({mode: 'html5'}, _videoLinkParser(url)); + } else { + result = { + mode: 'incorrect' + }; + } + + return result; + }; + + /** + * 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`, ...} + * ] + */ + var _getVideoList = function (links) { + if ($.isArray(links)) { + var arr = [], + data; + + for (var i = 0, len = links.length; i < len; i += 1) { + data = _linkParser(links[i]); + + if (data.mode !== 'incorrect') { + arr.push(data); + } + } + + return arr; + } + }; + + + /** + * 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({ + field_name: m.getFieldName() + }); + + if (model) { + model.setValue(m.getDisplayValue()); + } + }); + }; + + /** + * 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 + // subsequent invocations of _command() function. + // + // A new AJAX request will be made on each invocation of the + // _command() function. + var xhr = null; + + return function (action, locator, videoList, extraParams) { + var params, data; + + if (extraParams) { + if ($.isPlainObject(extraParams)) { + params = extraParams; + } else { + params = {params: extraParams}; + } + } + + data = $.extend( + { locator: locator }, + { videos: videoList }, + params + ); + + xhr = $.ajaxQueue({ + url: '/transcripts/' + action, + data: { data: JSON.stringify(data) }, + notifyOnError: false, + type: 'get' + }); + + return xhr; + }; + }()); + + return { + getField: _getField, + parseYoutubeLink: _youtubeParser, + parseHTML5Link: _videoLinkParser, + parseLink: _linkParser, + getYoutubeLink: _getYoutubeLink, + syncCollections: _syncCollections, + command: _command, + getVideoList: _getVideoList, + Storage: { + set: Storage.set, + get: Storage.get, + remove: Storage.remove + } + }; +}()); }); diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index ec9accca30..b640a73812 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -37,6 +37,7 @@ 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