diff --git a/common/lib/xmodule/xmodule/js/spec/helper.coffee b/common/lib/xmodule/xmodule/js/spec/helper.coffee index 1dfccdf521..f3cecf71cb 100644 --- a/common/lib/xmodule/xmodule/js/spec/helper.coffee +++ b/common/lib/xmodule/xmodule/js/spec/helper.coffee @@ -90,7 +90,13 @@ jasmine.stubbedHtml5Speeds = ['0.75', '1.0', '1.25', '1.50'] jasmine.stubRequests = -> spyOn($, 'ajax').andCallFake (settings) -> if match = settings.url.match /youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/ - settings.success data: jasmine.stubbedMetadata[match[1]] + if settings.success + # match[1] - it's video ID + settings.success data: jasmine.stubbedMetadata[match[1]] + else { + always: (callback) -> + callback.call(window, {}, 'success'); + } else if match = settings.url.match /static(\/.*)?\/subs\/(.+)\.srt\.sjson/ settings.success jasmine.stubbedCaption else if settings.url.match /.+\/problem_get$/ diff --git a/common/lib/xmodule/xmodule/js/spec/video/general_spec.js b/common/lib/xmodule/xmodule/js/spec/video/general_spec.js index accfba0dbe..9194106fff 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/general_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/general_spec.js @@ -4,8 +4,6 @@ beforeEach(function () { jasmine.stubRequests(); - oldOTBD = window.onTouchBasedDevice; - window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false); this.videosDefinition = '0.75:7tqY6eQzVhE,1.0:cogebirgzzM'; this['7tqY6eQzVhE'] = '7tqY6eQzVhE'; this['cogebirgzzM'] = 'cogebirgzzM'; @@ -16,7 +14,6 @@ window.onYouTubePlayerAPIReady = undefined; window.onHTML5PlayerAPIReady = undefined; $('source').remove(); - window.onTouchBasedDevice = oldOTBD; }); describe('constructor', function () { @@ -58,6 +55,46 @@ expect(this.state.speed).toEqual('0.75'); }); }); + + describe('Check Youtube link existence', function () { + var statusList = { + error: 'html5', + timeout: 'html5', + abort: 'html5', + parsererror: 'html5', + success: 'youtube', + notmodified: 'youtube' + }; + + function stubDeffered(data, status) { + return { + always: function(callback) { + callback.call(window, data, status); + } + } + } + + function checkPlayer(videoType, data, status) { + this.state = new window.Video('#example'); + spyOn(this.state , 'getVideoMetadata') + .andReturn(stubDeffered(data, status)); + this.state.initialize('#example'); + + expect(this.state.videoType).toEqual(videoType); + } + + it('if video id is incorrect', function () { + checkPlayer('html5', { error: {} }, 'success'); + }); + + $.each(statusList, function(status, mode){ + it('Status:' + status + ', mode:' + mode, function () { + checkPlayer(mode, {}, status); + }); + }); + + }); + }); describe('HTML5', function () { diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js index e92f251f70..0873426aa9 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js @@ -79,6 +79,8 @@ it('create Youtube player', function() { var oldYT = window.YT; + jasmine.stubRequests(); + window.YT = { Player: function () { }, PlayerState: oldYT.PlayerState diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index 9a6e20421d..79bc16dbda 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -30,8 +30,7 @@ function (VideoPlayer) { */ return function (state, element) { _makeFunctionsPublic(state); - _initialize(state, element); - _renderElements(state); + state.initialize(element); }; // *************************************************************** @@ -56,59 +55,12 @@ function (VideoPlayer) { // Old private functions. Now also public so that can be // tested by Jasmine. + state.initialize = _.bind(initialize, state); state.parseSpeed = _.bind(parseSpeed, state); state.fetchMetadata = _.bind(fetchMetadata, state); state.parseYoutubeStreams = _.bind(parseYoutubeStreams, state); state.parseVideoSources = _.bind(parseVideoSources, state); - } - - // function _initialize(element) - // The function set initial configuration and preparation. - - function _initialize(state, element) { - // This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'. - state.isFullScreen = false; - - // The parent element of the video, and the ID. - state.el = $(element).find('.video'); - state.id = state.el.attr('id').replace(/video_/, ''); - - // We store all settings passed to us by the server in one place. These are "read only", so don't - // modify them. All variable content lives in 'state' object. - state.config = { - element: element, - - start: state.el.data('start'), - end: state.el.data('end'), - - caption_data_dir: state.el.data('caption-data-dir'), - caption_asset_path: state.el.data('caption-asset-path'), - show_captions: (state.el.data('show-captions').toString().toLowerCase() === 'true'), - youtubeStreams: state.el.data('streams'), - - sub: state.el.data('sub'), - mp4Source: state.el.data('mp4-source'), - webmSource: state.el.data('webm-source'), - oggSource: state.el.data('ogg-source'), - - fadeOutTimeout: 1400, - - availableQualities: ['hd720', 'hd1080', 'highres'] - }; - - if (!(_parseYouTubeIDs(state))) { - // If we do not have YouTube ID's, try parsing HTML5 video sources. - _prepareHTML5Video(state); - } - - _configureCaptions(state); - _setPlayerMode(state); - - // Possible value are: 'visible', 'hiding', and 'invisible'. - state.controlState = 'visible'; - state.controlHideTimeout = null; - state.captionState = 'visible'; - state.captionHideTimeout = null; + state.getVideoMetadata = _.bind(getVideoMetadata, state); } // function _renderElements(state) @@ -228,12 +180,83 @@ function (VideoPlayer) { state.setSpeed($.cookie('video_speed')); } + function _setConfigurations(state) { + _configureCaptions(state); + _setPlayerMode(state); + + // Possible value are: 'visible', 'hiding', and 'invisible'. + state.controlState = 'visible'; + state.controlHideTimeout = null; + state.captionState = 'visible'; + state.captionHideTimeout = null; + } + // *************************************************************** // Public functions start here. // These are available via the 'state' object. Their context ('this' keyword) is the 'state' object. // The magic private function that makes them available and sets up their context is makeFunctionsPublic(). // *************************************************************** + // function initialize(element) + // The function set initial configuration and preparation. + + function initialize(element) { + var _this = this; + // This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'. + this.isFullScreen = false; + + // The parent element of the video, and the ID. + this.el = $(element).find('.video'); + this.id = this.el.attr('id').replace(/video_/, ''); + + // We store all settings passed to us by the server in one place. These are "read only", so don't + // modify them. All variable content lives in 'state' object. + this.config = { + element: element, + + start: this.el.data('start'), + end: this.el.data('end'), + + caption_data_dir: this.el.data('caption-data-dir'), + caption_asset_path: this.el.data('caption-asset-path'), + show_captions: (this.el.data('show-captions').toString().toLowerCase() === 'true'), + youtubeStreams: this.el.data('streams'), + + sub: this.el.data('sub'), + mp4Source: this.el.data('mp4-source'), + webmSource: this.el.data('webm-source'), + oggSource: this.el.data('ogg-source'), + + fadeOutTimeout: 1400, + + availableQualities: ['hd720', 'hd1080', 'highres'] + }; + + if (!(_parseYouTubeIDs(this))) { + // If we do not have YouTube ID's, try parsing HTML5 video sources. + _prepareHTML5Video(this); + _setConfigurations(this); + _renderElements(this); + } else { + this.getVideoMetadata() + .always(function(json, status) { + var err = $.isPlainObject(json.error) || + (status !== "success" && status !== "notmodified"); + + if (err){ + // When the youtube link doesn't work for any reason + // (for example, the great firewall in china) any + // alternate sources should automatically play. + _prepareHTML5Video(_this); + _this.el.find('a.quality_control').hide(); + } + + _setConfigurations(_this); + _renderElements(_this); + }); + } + } + // function parseYoutubeStreams(state, youtubeStreams) // // Take a string in the form: @@ -297,9 +320,9 @@ function (VideoPlayer) { this.metadata = {}; $.each(this.videos, function (speed, url) { - $.get('https://gdata.youtube.com/feeds/api/videos/' + url + '?v=2&alt=jsonc', (function(data) { + _this.getVideoMetadata(url, function(data) { _this.metadata[data.data.id] = data.data; - }), 'jsonp'); + }); }); } @@ -329,6 +352,24 @@ function (VideoPlayer) { } } + function getVideoMetadata(url, callback) { + var successHandler, xhr; + + if (typeof url !== 'string') { + url = this.videos['1.0'] || ''; + } + + successHandler = ($.isFunction(callback)) ? callback : null; + xhr = $.ajax({ + url: 'https://gdata.youtube.com/feeds/api/videos/' + url + '?v=2&alt=jsonc', + timeout: 500, + dataType: 'jsonp', + success: successHandler + }); + + return xhr; + } + function stopBuffering() { var video;