From aefb1aa9c872f16da9a32cf85ce6879e71869c29 Mon Sep 17 00:00:00 2001 From: Anton Stupak Date: Thu, 25 Jul 2013 15:00:50 +0300 Subject: [PATCH] Hides Error notification when subtitle file is missing Refactors bind and indexOf. Adds error message when specify the wrong selector. Adds some of the requested documentation. Refactors 01_initialize.js. Fixes to 03_video_player.js --- .../lib/xmodule/xmodule/js/spec/helper.coffee | 22 +- .../js/spec/videoalpha/video_caption_spec.js | 8 +- common/lib/xmodule/xmodule/js/src/.gitignore | 4 + .../js/src/videoalpha/01_helper_utils.js | 80 ----- .../{02_initialize.js => 01_initialize.js} | 288 ++++++++++-------- .../{03_html5_video.js => 02_html5_video.js} | 10 +- ...{04_video_player.js => 03_video_player.js} | 92 +++--- ...5_video_control.js => 04_video_control.js} | 18 +- ...control.js => 05_video_quality_control.js} | 10 +- ..._slider.js => 06_video_progress_slider.js} | 14 +- ..._control.js => 07_video_volume_control.js} | 6 +- ...d_control.js => 08_video_speed_control.js} | 8 +- ...0_video_caption.js => 09_video_caption.js} | 78 ++--- .../src/videoalpha/{11_main.js => 10_main.js} | 14 +- .../lib/xmodule/xmodule/videoalpha_module.py | 21 +- 15 files changed, 322 insertions(+), 351 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/js/src/videoalpha/01_helper_utils.js rename common/lib/xmodule/xmodule/js/src/videoalpha/{02_initialize.js => 01_initialize.js} (65%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{03_html5_video.js => 02_html5_video.js} (96%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{04_video_player.js => 03_video_player.js} (80%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{05_video_control.js => 04_video_control.js} (90%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{06_video_quality_control.js => 05_video_quality_control.js} (90%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{07_video_progress_slider.js => 06_video_progress_slider.js} (89%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{08_video_volume_control.js => 07_video_volume_control.js} (95%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{09_video_speed_control.js => 08_video_speed_control.js} (94%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{10_video_caption.js => 09_video_caption.js} (81%) rename common/lib/xmodule/xmodule/js/src/videoalpha/{11_main.js => 10_main.js} (87%) diff --git a/common/lib/xmodule/xmodule/js/spec/helper.coffee b/common/lib/xmodule/xmodule/js/spec/helper.coffee index 4e7445ccd1..8f2135083d 100644 --- a/common/lib/xmodule/xmodule/js/spec/helper.coffee +++ b/common/lib/xmodule/xmodule/js/spec/helper.coffee @@ -10,7 +10,7 @@ window.YT = window.STATUS = window.YT.PlayerState -oldGetWithPrefix = window.jQuery.getWithPrefix +oldAjaxWithPrefix = window.jQuery.ajaxWithPrefix jasmine.stubbedCaption = end: [3120, 6270, 8490, 21620, 24920, 25750, 27900, 34380, 35550, 40250] @@ -28,8 +28,9 @@ jasmine.stubbedCaption = "And some will have quite a bit of energy, just for a moment." ] -# For our purposes, we need to make sure that the function $.getWithPrefix doe not fail -# when during tests a captions file is requested. It is originally defined in +# For our purposes, we need to make sure that the function $.ajaxWithPrefix +# does not fail when during tests a captions file is requested. +# It is originally defined in # # common/static/coffee/src/ajax_prefix.js # @@ -37,14 +38,21 @@ jasmine.stubbedCaption = # # 1.) Return a hard coded captions object if the file name contains 'test_name_of_the_subtitles'. # 2.) Behaves the same a as the origianl in all other cases. -window.jQuery.getWithPrefix = (url, data, callback, type) -> + +window.jQuery.ajaxWithPrefix = (url, settings) -> + if not settings + settings = url + url = settings.url + success = settings.success + data = settings.data + if url.match(/test_name_of_the_subtitles/g) isnt null or url.match(/slowerSpeedYoutubeId/g) isnt null or url.match(/normalSpeedYoutubeId/g) isnt null - if window.jQuery.isFunction(callback) is true - callback jasmine.stubbedCaption + if window.jQuery.isFunction(success) is true + success jasmine.stubbedCaption else if window.jQuery.isFunction(data) is true data jasmine.stubbedCaption else - oldGetWithPrefix.apply this, arguments + oldAjaxWithPrefix.apply @, arguments # Time waitsFor() should wait for before failing a test. window.WAIT_TIMEOUT = 1000 diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/video_caption_spec.js index b7ba8f585f..6841f584b8 100644 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/video_caption_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/video_caption_spec.js @@ -27,7 +27,7 @@ describe('constructor', function() { describe('always', function() { beforeEach(function() { - spyOn($, 'getWithPrefix').andCallThrough(); + spyOn($, 'ajaxWithPrefix').andCallThrough(); initialize(); }); @@ -49,7 +49,11 @@ }, 'Expect captions to be loaded.', 1000); runs(function () { - expect($.getWithPrefix).toHaveBeenCalledWith(videoCaption.captionURL(), jasmine.any(Function)); + expect($.ajaxWithPrefix).toHaveBeenCalledWith({ + url: videoCaption.captionURL(), + notifyOnError: false, + success: jasmine.any(Function) + }); }); }); diff --git a/common/lib/xmodule/xmodule/js/src/.gitignore b/common/lib/xmodule/xmodule/js/src/.gitignore index 456e71bf8b..85b7a639d5 100644 --- a/common/lib/xmodule/xmodule/js/src/.gitignore +++ b/common/lib/xmodule/xmodule/js/src/.gitignore @@ -2,3 +2,7 @@ # For each of the xmodules subdirectories, add a .gitignore file that # will version any *.js file that is specifically written, not compiled. *.js + + +# Videoalpha are written in pure JavaScript. +!videoalpha/*.js \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/01_helper_utils.js b/common/lib/xmodule/xmodule/js/src/videoalpha/01_helper_utils.js deleted file mode 100644 index 1632ba9e5c..0000000000 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/01_helper_utils.js +++ /dev/null @@ -1,80 +0,0 @@ -// IE browser supports Function.bind() only starting with version 9. -// -// The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all -// browsers. You can partially work around this by inserting the following code at the beginning of your -// scripts, allowing use of much of the functionality of bind() in implementations that do not natively support -// it. -// -// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind -if (!Function.prototype.bind) { - Function.prototype.bind = function (oThis) { - var aArgs, fToBind, fNOP, fBound; - - if (typeof this !== 'function') { - // closest thing possible to the ECMAScript 5 internal IsCallable function - throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); - } - - aArgs = Array.prototype.slice.call(arguments, 1); - fToBind = this; - fNOP = function () {}; - fBound = function () { - return fToBind.apply( - this instanceof fNOP && oThis ? this : oThis, - aArgs.concat(Array.prototype.slice.call(arguments)) - ); - }; - - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); - - return fBound; - }; -} - -// IE browser supports Array.indexOf() only starting with version 9. -// -// indexOf is a recent addition to the ECMA-262 standard; as such it may not be present in all browsers. You can work -// around this by utilizing the following code at the beginning of your scripts. This will allow you to use indexOf -// when there is still no native support. This algorithm matches the one specified in ECMA-262, 5th edition, assuming -// Object, TypeError, Number, Math.floor, Math.abs, and Math.max have their original values. -// -// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf -if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - 'use strict'; - if (this == null) { - throw new TypeError(); - } - var t = Object(this); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 1) { - n = Number(arguments[1]); - if (n != n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - } -} - -if (!window.onTouchBasedDevice) { - window.onTouchBasedDevice = function() { - return navigator.userAgent.match(/iPhone|iPod|iPad/i); - }; -} diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/02_initialize.js b/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js similarity index 65% rename from common/lib/xmodule/xmodule/js/src/videoalpha/02_initialize.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js index 262588551d..19a7de4cc8 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/02_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js @@ -12,8 +12,8 @@ (function (requirejs, require, define) { define( -'videoalpha/02_initialize.js', -['videoalpha/04_video_player.js'], +'videoalpha/01_initialize.js', +['videoalpha/03_video_player.js'], function (VideoPlayer) { /** @@ -25,70 +25,66 @@ function (VideoPlayer) { * @param {DOM element} element Container of the entire Video Alpha DOM element. */ return function (state, element) { - makeFunctionsPublic(state); - state.renderElements(element); + _makeFunctionsPublic(state); + _initialize(state, element); + _renderElements(state); }; // *************************************************************** - // Private functions start here. + // Private functions start here. Private functions start with underscore. // *************************************************************** /** - * @function makeFunctionsPublic + * @function _makeFunctionsPublic * * Functions which will be accessible via 'state' object. When called, these functions will get the 'state' * object as a context. * * @param {Object} state A place for all properties, and methods of Video Alpha. */ - function makeFunctionsPublic(state) { - state.setSpeed = setSpeed.bind(state); - state.youtubeId = youtubeId.bind(state); - state.getDuration = getDuration.bind(state); - state.trigger = trigger.bind(state); + function _makeFunctionsPublic(state) { + state.setSpeed = _.bind(setSpeed, state); + state.youtubeId = _.bind(youtubeId, state); + state.getDuration = _.bind(getDuration, state); + state.trigger = _.bind(trigger, state); // Old private functions. Now also public so that can be // tested by Jasmine. - state.renderElements = renderElements.bind(state); - state.parseSpeed = parseSpeed.bind(state); - state.fetchMetadata = fetchMetadata.bind(state); - state.parseYoutubeStreams = parseYoutubeStreams.bind(state); - state.parseVideoSources = parseVideoSources.bind(state); + + state.parseSpeed = _.bind(parseSpeed, state); + state.fetchMetadata = _.bind(fetchMetadata, state); + state.parseYoutubeStreams = _.bind(parseYoutubeStreams, state); + state.parseVideoSources = _.bind(parseVideoSources, state); } - // function renderElements(state) - // - // Create any necessary DOM elements, attach them, and set their initial configuration. Also - // make the created DOM elements available via the 'state' object. Much easier to work this - // way - you don't have to do repeated jQuery element selects. - function renderElements(element) { - var onPlayerReadyFunc, - _this = this; + // 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'. - this.isFullScreen = false; + state.isFullScreen = false; // The parent element of the video, and the ID. - this.el = $(element).find('.videoalpha'); - this.id = this.el.attr('id').replace(/video_/, ''); + state.el = $(element).find('.videoalpha'); + 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. - this.config = { + state.config = { element: element, - start: this.el.data('start'), - end: this.el.data('end'), + start: state.el.data('start'), + end: state.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'), + 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: this.el.data('sub'), - mp4Source: this.el.data('mp4-source'), - webmSource: this.el.data('webm-source'), - oggSource: this.el.data('ogg-source'), + 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, @@ -101,111 +97,147 @@ function (VideoPlayer) { } }, - inCms: this.el.data('in-studio') + inCms: state.el.data('in-studio') }; - // Try to parse YouTube stream ID's. If - if (this.parseYoutubeStreams(this.config.youtubeStreams)) { - this.videoType = 'youtube'; - - this.fetchMetadata(); - this.parseSpeed(); + if (!(_parseYouTubeIDs(state))) { + // If we do not have YouTube ID's, try parsing HTML5 video sources. + _prepareHTML5Video(state); } - // If we do not have YouTube ID's, try parsing HTML5 video sources. - else { - this.videoType = 'html5'; - - this.parseVideoSources( - { - mp4: this.config.mp4Source, - webm: this.config.webmSource, - ogg: this.config.oggSource - } - ); - - if (!this.config.sub || !this.config.sub.length) { - this.config.sub = ''; - this.config.show_captions = false; - } - - this.speeds = ['0.75', '1.0', '1.25', '1.50']; - this.videos = { - '0.75': this.config.sub, - '1.0': this.config.sub, - '1.25': this.config.sub, - '1.5': this.config.sub - }; - - this.setSpeed($.cookie('video_speed')); - } - - // Configure displaying of captions. - // - // Option - // - // this.config.show_captions = true | false - // - // defines whether to turn off/on the captions altogether. User will not have the ability to turn them on/off. - // - // Option - // - // this.hide_captions = true | false - // - // represents the user's choice of having the subtitles shown or hidden. This choice is stored in cookies. - if (this.config.show_captions) { - this.hide_captions = ($.cookie('hide_captions') === 'true'); - } else { - this.hide_captions = true; - - $.cookie('hide_captions', this.hide_captions, { - expires: 3650, - path: '/' - }); - - this.el.addClass('closed'); - } - - // By default we will be forcing HTML5 player mode. Only in the case when, after initializtion, we will - // get one available playback rate, we will change to Flash player mode. There is a need to store this - // setting in cookies because otherwise we will have to change from HTML5 to Flash on every page load - // in a browser that doesn't fully support HTML5. When we have this setting in cookies, we can select - // the proper mode from the start (not having to change mode later on). - (function (currentPlayerMode) { - if ((currentPlayerMode === 'html5') || (currentPlayerMode === 'flash')) { - _this.currentPlayerMode = currentPlayerMode; - } else { - $.cookie('current_player_mode', 'html5', { - expires: 3650, - path: '/' - }); - _this.currentPlayerMode = 'html5'; - } - }($.cookie('current_player_mode'))); + _configureCaptions(state); + _setPlayerMode(state); // Possible value are: 'visible', 'hiding', and 'invisible'. - this.controlState = 'visible'; - this.controlHideTimeout = null; - this.captionState = 'visible'; - this.captionHideTimeout = null; + state.controlState = 'visible'; + state.controlHideTimeout = null; + state.captionState = 'visible'; + state.captionHideTimeout = null; + } + // function _renderElements(state) + // + // Create any necessary DOM elements, attach them, and set their initial configuration. Also + // make the created DOM elements available via the 'state' object. Much easier to work this + // way - you don't have to do repeated jQuery element selects. + function _renderElements(state) { // Launch embedding of actual video content, or set it up so that it will be done as soon as the // appropriate video player (YouTube or stand alone HTML5) is loaded, and can handle embedding. // // Note that the loading of stand alone HTML5 player API is handled by Require JS. At the time // when we reach this code, the stand alone HTML5 player is already loaded, so no further testing // in that case is required. + var onPlayerReadyFunc; if ( - ((this.videoType === 'youtube') && (window.YT) && (window.YT.Player)) || - (this.videoType === 'html5') + ((state.videoType === 'youtube') && (window.YT) && (window.YT.Player)) || + (state.videoType === 'html5') ) { - VideoPlayer(this); + VideoPlayer(state); } else { onPlayerReadyFunc = (this.videoType === 'youtube') ? 'onYouTubePlayerAPIReady' : 'onHTML5PlayerAPIReady'; - window[onPlayerReadyFunc] = VideoPlayer.bind(window, this); + window[onPlayerReadyFunc] = _.bind(window.VideoPlayer, state); } } + // function _configureCaptions(state) + // Configure displaying of captions. + // + // Option + // this.config.show_captions = true | false + // + // defines whether to turn off/on the captions altogether. User will not have the ability to turn them on/off. + // + // Option + // this.hide_captions = true | false + // + // represents the user's choice of having the subtitles shown or hidden. This choice is stored in cookies. + function _configureCaptions(state) { + if (state.config.show_captions) { + state.hide_captions = ($.cookie('hide_captions') === 'true'); + } else { + state.hide_captions = true; + + $.cookie('hide_captions', state.hide_captions, { + expires: 3650, + path: '/' + }); + + state.el.addClass('closed'); + } + } + + // function _setPlayerMode(state) + // By default we will be forcing HTML5 player mode. Only in the case when, after initializtion, we will + // get one available playback rate, we will change to Flash player mode. There is a need to store this + // setting in cookies because otherwise we will have to change from HTML5 to Flash on every page load + // in a browser that doesn't fully support HTML5. When we have this setting in cookies, we can select + // the proper mode from the start (not having to change mode later on). + function _setPlayerMode(state) { + (function (currentPlayerMode) { + if ((currentPlayerMode === 'html5') || (currentPlayerMode === 'flash')) { + state.currentPlayerMode = currentPlayerMode; + } else { + $.cookie('current_player_mode', 'html5', { + expires: 3650, + path: '/' + }); + state.currentPlayerMode = 'html5'; + } + }($.cookie('current_player_mode'))); + } + + // function _parseYouTubeIDs(state) + // The function parse YouTube stream ID's. + // @return + // false: We don't have YouTube video IDs to work with; most likely we have HTML5 video sources. + // true: Parsing of YouTube video IDs went OK, and we can proceed onwards to play YouTube videos. + function _parseYouTubeIDs(state) { + if (state.parseYoutubeStreams(state.config.youtubeStreams)) { + state.videoType = 'youtube'; + + state.fetchMetadata(); + state.parseSpeed(); + return true; + } + return false; + } + + // function _prepareHTML5Video(state) + // The function prepare HTML5 video, parse HTML5 + // video sources etc. + function _prepareHTML5Video(state) { + state.videoType = 'html5'; + + state.parseVideoSources( + { + mp4: state.config.mp4Source, + webm: state.config.webmSource, + ogg: state.config.oggSource + } + ); + + if (!state.config.sub || !state.config.sub.length) { + state.config.sub = ''; + state.config.show_captions = false; + } + + state.speeds = ['0.75', '1.0', '1.25', '1.50']; + state.videos = { + '0.75': state.config.sub, + '1.0': state.config.sub, + '1.25': state.config.sub, + '1.5': state.config.sub + }; + + state.setSpeed($.cookie('video_speed')); + } + + // *************************************************************** + // 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 parseYoutubeStreams(state, youtubeStreams) // // Take a string in the form: @@ -286,14 +318,8 @@ function (VideoPlayer) { this.setSpeed($.cookie('video_speed')); } - // *************************************************************** - // 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 setSpeed(newSpeed, updateCookie) { - if (this.speeds.indexOf(newSpeed) !== -1) { + if (_.indexOf(this.speeds, newSpeed) !== -1) { this.speed = newSpeed; } else { this.speed = '1.0'; @@ -315,7 +341,7 @@ function (VideoPlayer) { return this.metadata[this.youtubeId()].duration; } - /* he function .trigger() expects the parameter @callType one of + /* The function .trigger() expects the parameter @callType one of * * 'event' * 'method' diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/03_html5_video.js b/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js similarity index 96% rename from common/lib/xmodule/xmodule/js/src/videoalpha/03_html5_video.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js index 0f4bf904b6..ed76065efb 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/03_html5_video.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js @@ -14,7 +14,7 @@ (function (requirejs, require, define) { define( -'videoalpha/03_html5_video.js', +'videoalpha/02_html5_video.js', [], function () { var HTML5Video = {}; @@ -128,7 +128,7 @@ function () { * } */ function Player(el, config) { - var sourceStr, _this; + var sourceStr, _this, errorMessage; // Initially we assume that el is a DOM element. If jQuery selector fails to select something, we // assume that el is an ID of a DOM element. We try to select by ID. If jQuery fails this time, @@ -139,6 +139,12 @@ function () { this.el = $('#' + el); if (this.el.length === 0) { + errorMessage = 'VideoPlayer: Element corresponding to the given selector does not found.'; + if (window.console && console.log) { + console.log(errorMessage); + } else { + throw new Error(errorMessage); + } return; } } diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_player.js b/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js similarity index 80% rename from common/lib/xmodule/xmodule/js/src/videoalpha/04_video_player.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js index 62fb7ce797..fbbdb20e58 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_player.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js @@ -2,8 +2,8 @@ // VideoPlayer module. define( -'videoalpha/04_video_player.js', -['videoalpha/03_html5_video.js'], +'videoalpha/03_video_player.js', +['videoalpha/02_html5_video.js'], function (HTML5Video) { // VideoPlayer() function - what this module "exports". @@ -12,7 +12,7 @@ function (HTML5Video) { makeFunctionsPublic(state); renderElements(state); - bindHandlers(); + // No callbacks to DOM events (click, mousemove, etc.). }; // *************************************************************** @@ -24,25 +24,25 @@ function (HTML5Video) { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoPlayer.pause = pause.bind(state); - state.videoPlayer.play = play.bind(state); - state.videoPlayer.update = update.bind(state); - state.videoPlayer.onSpeedChange = onSpeedChange.bind(state); - state.videoPlayer.onCaptionSeek = onSeek.bind(state); - state.videoPlayer.onSlideSeek = onSeek.bind(state); - state.videoPlayer.onEnded = onEnded.bind(state); - state.videoPlayer.onPause = onPause.bind(state); - state.videoPlayer.onPlay = onPlay.bind(state); - state.videoPlayer.onUnstarted = onUnstarted.bind(state); - state.videoPlayer.handlePlaybackQualityChange = handlePlaybackQualityChange.bind(state); - state.videoPlayer.onPlaybackQualityChange = onPlaybackQualityChange.bind(state); - state.videoPlayer.onStateChange = onStateChange.bind(state); - state.videoPlayer.onReady = onReady.bind(state); - state.videoPlayer.updatePlayTime = updatePlayTime.bind(state); - state.videoPlayer.isPlaying = isPlaying.bind(state); - state.videoPlayer.log = log.bind(state); - state.videoPlayer.duration = duration.bind(state); - state.videoPlayer.onVolumeChange = onVolumeChange.bind(state); + state.videoPlayer.pause = _.bind(pause, state); + state.videoPlayer.play = _.bind(play, state); + state.videoPlayer.update = _.bind(update, state); + state.videoPlayer.onSpeedChange = _.bind(onSpeedChange, state); + state.videoPlayer.onCaptionSeek = _.bind(onSeek, state); + state.videoPlayer.onSlideSeek = _.bind(onSeek, state); + state.videoPlayer.onEnded = _.bind(onEnded, state); + state.videoPlayer.onPause = _.bind(onPause, state); + state.videoPlayer.onPlay = _.bind(onPlay, state); + state.videoPlayer.onUnstarted = _.bind(onUnstarted, state); + state.videoPlayer.handlePlaybackQualityChange = _.bind(handlePlaybackQualityChange, state); + state.videoPlayer.onPlaybackQualityChange = _.bind(onPlaybackQualityChange, state); + state.videoPlayer.onStateChange = _.bind(onStateChange, state); + state.videoPlayer.onReady = _.bind(onReady, state); + state.videoPlayer.updatePlayTime = _.bind(updatePlayTime, state); + state.videoPlayer.isPlaying = _.bind(isPlaying, state); + state.videoPlayer.log = _.bind(log, state); + state.videoPlayer.duration = _.bind(duration, state); + state.videoPlayer.onVolumeChange = _.bind(onVolumeChange, state); } // function renderElements(state) @@ -123,19 +123,12 @@ function (HTML5Video) { } } - // function bindHandlers(state) - // - // Bind any necessary function callbacks to DOM events (click, mousemove, etc.). - function bindHandlers() { - - } - - // function reinitAsFlash(state) + // function restartUsingFlash(state) // // When we are about to play a YouTube video in HTML5 mode and discover that we only // have one available playback rate, we will switch to Flash mode. In Flash speed - // switching is done by reloading videos recorded at differtn frame rates. - function reinitAsFlash(state) { + // switching is done by reloading videos recorded at different frame rates. + function restartUsingFlash(state) { // Remove from the page current iFrame with HTML5 video. state.videoPlayer.player.destroy(); @@ -149,7 +142,7 @@ function (HTML5Video) { // Removed configuration option that requests the HTML5 mode. delete state.videoPlayer.playerVars.html5; - // Reuqest for the creation of a new Flash player + // Request for the creation of a new Flash player state.videoPlayer.player = new YT.Player(state.id, { 'playerVars': state.videoPlayer.playerVars, 'videoId': state.youtubeId(), @@ -179,6 +172,9 @@ function (HTML5Video) { } } + // This function gets the video's current play position in time + // (currentTime) and its duration. + // It is called at a regular interval when the video is playing (see below). function update() { this.videoPlayer.currentTime = this.videoPlayer.player.getCurrentTime(); @@ -187,11 +183,6 @@ function (HTML5Video) { } } - // We request the reloading of the video in the case when YouTube is in - // Flash player mode, or when we are in Firefox, and the new speed is 1.0. - // The second case is necessary to avoid the bug where in Firefox speed - // switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube - // API. function onSpeedChange(newSpeed, updateCookie) { if (this.currentPlayerMode === 'flash') { this.videoPlayer.currentTime = Time.convert( @@ -218,7 +209,12 @@ function (HTML5Video) { !(this.browserIsFirefox && newSpeed === '1.0' && this.videoType === 'youtube') ) { this.videoPlayer.player.setPlaybackRate(newSpeed); - } else { // if (this.currentPlayerMode === 'flash') { + } else { + // We request the reloading of the video in the case when YouTube is in + // Flash player mode, or when we are in Firefox, and the new speed is 1.0. + // The second case is necessary to avoid the bug where in Firefox speed + // switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube + // API. if (this.videoPlayer.isPlaying()) { this.videoPlayer.player.loadVideoById(this.youtubeId(), this.videoPlayer.currentTime); } else { @@ -229,6 +225,10 @@ function (HTML5Video) { } } + // Every 200 ms, if the video is playing, we call the function update, via + // clearInterval. This interval is called updateInterval. + // It is created on a onPlay event. Cleared on a onPause event. + // Reinitialized on a onSeek event. function onSeek(params) { this.videoPlayer.log( 'seek_video', @@ -318,18 +318,24 @@ function (HTML5Video) { availablePlaybackRates = this.videoPlayer.player.getAvailablePlaybackRates(); if ((this.currentPlayerMode === 'html5') && (this.videoType === 'youtube')) { if (availablePlaybackRates.length === 1) { - reinitAsFlash(this); + restartUsingFlash(this); return; } else if (availablePlaybackRates.length > 1) { - // We need to synchronize available frame rates with the ones that the user specified. + // We need to synchronize available frame rates with the ones + // that the user specified. baseSpeedSubs = this.videos['1.0']; _this = this; + // this.videos is a dictionary containing various frame rates + // and their associated subs. + + // First clear the dictionary. $.each(this.videos, function(index, value) { delete _this.videos[index]; }); this.speeds = []; + // Recreate it with the supplied frame rates. $.each(availablePlaybackRates, function(index, value) { _this.videos[value.toFixed(2).replace(/\.00$/, '.0')] = baseSpeedSubs; @@ -411,10 +417,8 @@ function (HTML5Video) { if (this.videoType === 'youtube') { logInfo.code = this.youtubeId(); - } else { - if (this.videoType === 'html5') { + } else if (this.videoType === 'html5') { logInfo.code = 'html5'; - } } Logger.log(eventName, logInfo); diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js similarity index 90% rename from common/lib/xmodule/xmodule/js/src/videoalpha/05_video_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js index b5a8bea7ce..fe72d55b6d 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js @@ -2,7 +2,7 @@ // VideoControl module. define( -'videoalpha/05_video_control.js', +'videoalpha/04_video_control.js', [], function () { @@ -24,14 +24,14 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoControl.showControls = showControls.bind(state); - state.videoControl.hideControls = hideControls.bind(state); - state.videoControl.play = play.bind(state); - state.videoControl.pause = pause.bind(state); - state.videoControl.togglePlayback = togglePlayback.bind(state); - state.videoControl.toggleFullScreen = toggleFullScreen.bind(state); - state.videoControl.exitFullScreen = exitFullScreen.bind(state); - state.videoControl.updateVcrVidTime = updateVcrVidTime.bind(state); + state.videoControl.showControls = _.bind(showControls,state); + state.videoControl.hideControls = _.bind(hideControls,state); + state.videoControl.play = _.bind(play,state); + state.videoControl.pause = _.bind(pause,state); + state.videoControl.togglePlayback = _.bind(togglePlayback,state); + state.videoControl.toggleFullScreen = _.bind(toggleFullScreen,state); + state.videoControl.exitFullScreen = _.bind(exitFullScreen,state); + state.videoControl.updateVcrVidTime = _.bind(updateVcrVidTime,state); } // function renderElements(state) diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_quality_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js similarity index 90% rename from common/lib/xmodule/xmodule/js/src/videoalpha/06_video_quality_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js index b22585c89e..6ea48cea9f 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_quality_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js @@ -2,7 +2,7 @@ // VideoQualityControl module. define( -'videoalpha/06_video_quality_control.js', +'videoalpha/05_video_quality_control.js', [], function () { @@ -29,8 +29,8 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoQualityControl.onQualityChange = onQualityChange.bind(state); - state.videoQualityControl.toggleQuality = toggleQuality.bind(state); + state.videoQualityControl.onQualityChange = _.bind(onQualityChange, state); + state.videoQualityControl.toggleQuality = _.bind(toggleQuality, state); } // function renderElements(state) @@ -65,7 +65,7 @@ function () { function onQualityChange(value) { this.videoQualityControl.quality = value; - if (this.config.availableQualities.indexOf(value) !== -1) { + if (_.indexOf(this.config.availableQualities, value) !== -1) { this.videoQualityControl.el.addClass('active'); } else { this.videoQualityControl.el.removeClass('active'); @@ -84,7 +84,7 @@ function () { event.preventDefault(); - if (this.config.availableQualities.indexOf(value) !== -1) { + if (_.indexOf(this.config.availableQualities, value) !== -1) { newQuality = 'large'; } else { newQuality = 'hd720'; diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js similarity index 89% rename from common/lib/xmodule/xmodule/js/src/videoalpha/07_video_progress_slider.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js index b3a11f86e7..219d5b73a5 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_progress_slider.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js @@ -9,7 +9,7 @@ mind, or whether to act, and in acting, to live." // VideoProgressSlider module. define( -'videoalpha/07_video_progress_slider.js', +'videoalpha/06_video_progress_slider.js', [], function () { @@ -31,13 +31,13 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoProgressSlider.onSlide = onSlide.bind(state); - state.videoProgressSlider.onChange = onChange.bind(state); - state.videoProgressSlider.onStop = onStop.bind(state); - state.videoProgressSlider.updateTooltip = updateTooltip.bind(state); - state.videoProgressSlider.updatePlayTime = updatePlayTime.bind(state); + state.videoProgressSlider.onSlide = _.bind(onSlide, state); + state.videoProgressSlider.onChange = _.bind(onChange, state); + state.videoProgressSlider.onStop = _.bind(onStop, state); + state.videoProgressSlider.updateTooltip = _.bind(updateTooltip, state); + state.videoProgressSlider.updatePlayTime = _.bind(updatePlayTime, state); //Added for tests -- JM - state.videoProgressSlider.buildSlider = buildSlider.bind(state); + state.videoProgressSlider.buildSlider = _.bind(buildSlider, state); } // function renderElements(state) diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js similarity index 95% rename from common/lib/xmodule/xmodule/js/src/videoalpha/08_video_volume_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js index 24b3054a4d..64c80b8324 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js @@ -2,7 +2,7 @@ // VideoVolumeControl module. define( -'videoalpha/08_video_volume_control.js', +'videoalpha/07_video_volume_control.js', [], function () { @@ -24,8 +24,8 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoVolumeControl.onChange = onChange.bind(state); - state.videoVolumeControl.toggleMute = toggleMute.bind(state); + state.videoVolumeControl.onChange = _.bind(onChange, state); + state.videoVolumeControl.toggleMute = _.bind(toggleMute, state); } // function renderElements(state) diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_speed_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js similarity index 94% rename from common/lib/xmodule/xmodule/js/src/videoalpha/09_video_speed_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js index 2eb2cddc93..a6fc90d2d0 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_speed_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js @@ -2,7 +2,7 @@ // VideoSpeedControl module. define( -'videoalpha/09_video_speed_control.js', +'videoalpha/08_video_speed_control.js', [], function () { @@ -24,9 +24,9 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoSpeedControl.changeVideoSpeed = changeVideoSpeed.bind(state); - state.videoSpeedControl.setSpeed = setSpeed.bind(state); - state.videoSpeedControl.reRender = reRender.bind(state); + state.videoSpeedControl.changeVideoSpeed = _.bind(changeVideoSpeed, state); + state.videoSpeedControl.setSpeed = _.bind(setSpeed, state); + state.videoSpeedControl.reRender = _.bind(reRender, state); } // function renderElements(state) diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/10_video_caption.js b/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js similarity index 81% rename from common/lib/xmodule/xmodule/js/src/videoalpha/10_video_caption.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js index 0e41f9820d..dc0dc440d1 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/10_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js @@ -2,7 +2,7 @@ // VideoCaption module. define( -'videoalpha/10_video_caption.js', +'videoalpha/09_video_caption.js', [], function () { @@ -24,30 +24,30 @@ function () { // Functions which will be accessible via 'state' object. When called, these functions will // get the 'state' object as a context. function makeFunctionsPublic(state) { - state.videoCaption.autoShowCaptions = autoShowCaptions.bind(state); - state.videoCaption.autoHideCaptions = autoHideCaptions.bind(state); - state.videoCaption.resize = resize.bind(state); - state.videoCaption.toggle = toggle.bind(state); - state.videoCaption.onMouseEnter = onMouseEnter.bind(state); - state.videoCaption.onMouseLeave = onMouseLeave.bind(state); - state.videoCaption.onMovement = onMovement.bind(state); - state.videoCaption.renderCaption = renderCaption.bind(state); - state.videoCaption.captionHeight = captionHeight.bind(state); - state.videoCaption.topSpacingHeight = topSpacingHeight.bind(state); - state.videoCaption.bottomSpacingHeight = bottomSpacingHeight.bind(state); - state.videoCaption.scrollCaption = scrollCaption.bind(state); - state.videoCaption.search = search.bind(state); - state.videoCaption.play = play.bind(state); - state.videoCaption.pause = pause.bind(state); - state.videoCaption.seekPlayer = seekPlayer.bind(state); - state.videoCaption.hideCaptions = hideCaptions.bind(state); - state.videoCaption.calculateOffset = calculateOffset.bind(state); - state.videoCaption.updatePlayTime = updatePlayTime.bind(state); + state.videoCaption.autoShowCaptions = _.bind(autoShowCaptions, state); + state.videoCaption.autoHideCaptions = _.bind(autoHideCaptions, state); + state.videoCaption.resize = _.bind(resize, state); + state.videoCaption.toggle = _.bind(toggle, state); + state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state); + state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state); + state.videoCaption.onMovement = _.bind(onMovement, state); + state.videoCaption.renderCaption = _.bind(renderCaption, state); + state.videoCaption.captionHeight = _.bind(captionHeight, state); + state.videoCaption.topSpacingHeight = _.bind(topSpacingHeight, state); + state.videoCaption.bottomSpacingHeight = _.bind(bottomSpacingHeight, state); + state.videoCaption.scrollCaption = _.bind(scrollCaption, state); + state.videoCaption.search = _.bind(search, state); + state.videoCaption.play = _.bind(play, state); + state.videoCaption.pause = _.bind(pause, state); + state.videoCaption.seekPlayer = _.bind(seekPlayer, state); + state.videoCaption.hideCaptions = _.bind(hideCaptions, state); + state.videoCaption.calculateOffset = _.bind(calculateOffset, state); + state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state); - state.videoCaption.renderElements = renderElements.bind(state); - state.videoCaption.bindHandlers = bindHandlers.bind(state); - state.videoCaption.fetchCaption = fetchCaption.bind(state); - state.videoCaption.captionURL = captionURL.bind(state); + state.videoCaption.renderElements = _.bind(renderElements, state); + state.videoCaption.bindHandlers = _.bind(bindHandlers, state); + state.videoCaption.fetchCaption = _.bind(fetchCaption, state); + state.videoCaption.captionURL = _.bind(captionURL, state); } // function renderElements() @@ -109,27 +109,27 @@ function () { } function fetchCaption() { - var _this = this, jQueryObject; + var _this = this; this.videoCaption.hideCaptions(this.hide_captions); - jQueryObject = $.getWithPrefix(this.videoCaption.captionURL(), function(captions) { - _this.videoCaption.captions = captions.text; - _this.videoCaption.start = captions.start; - _this.videoCaption.loaded = true; + $.ajaxWithPrefix({ + url: _this.videoCaption.captionURL(), + notifyOnError: false, + success: function(captions) { + _this.videoCaption.captions = captions.text; + _this.videoCaption.start = captions.start; + _this.videoCaption.loaded = true; - if (onTouchBasedDevice()) { - _this.videoCaption.subtitlesEl.find('li').html( - 'Caption will be displayed when you start playing the video.' - ); - } else { - _this.videoCaption.renderCaption(); + if (onTouchBasedDevice()) { + _this.videoCaption.subtitlesEl.find('li').html( + 'Caption will be displayed when you start playing the video.' + ); + } else { + _this.videoCaption.renderCaption(); + } } }); - - if (typeof jQueryObject === 'undefined') { - console.error('Subtitles not found. Upload subtitles to server!'); - } } function captionURL() { diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/11_main.js b/common/lib/xmodule/xmodule/js/src/videoalpha/10_main.js similarity index 87% rename from common/lib/xmodule/xmodule/js/src/videoalpha/11_main.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/10_main.js index 190eaed52a..3896f1d39c 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/11_main.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/10_main.js @@ -3,13 +3,13 @@ // Main module. require( [ - 'videoalpha/02_initialize.js', - 'videoalpha/05_video_control.js', - 'videoalpha/06_video_quality_control.js', - 'videoalpha/07_video_progress_slider.js', - 'videoalpha/08_video_volume_control.js', - 'videoalpha/09_video_speed_control.js', - 'videoalpha/10_video_caption.js' + 'videoalpha/01_initialize.js', + 'videoalpha/04_video_control.js', + 'videoalpha/05_video_quality_control.js', + 'videoalpha/06_video_progress_slider.js', + 'videoalpha/07_video_volume_control.js', + 'videoalpha/08_video_speed_control.js', + 'videoalpha/09_video_caption.js' ], function ( Initialize, diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py index 52f02082c1..cdf48f7705 100644 --- a/common/lib/xmodule/xmodule/videoalpha_module.py +++ b/common/lib/xmodule/xmodule/videoalpha_module.py @@ -69,17 +69,16 @@ class VideoAlphaModule(VideoAlphaFields, XModule): js = { 'js': [ - resource_string(__name__, 'js/src/videoalpha/01_helper_utils.js'), - resource_string(__name__, 'js/src/videoalpha/02_initialize.js'), - resource_string(__name__, 'js/src/videoalpha/03_html5_video.js'), - resource_string(__name__, 'js/src/videoalpha/04_video_player.js'), - resource_string(__name__, 'js/src/videoalpha/05_video_control.js'), - resource_string(__name__, 'js/src/videoalpha/06_video_quality_control.js'), - resource_string(__name__, 'js/src/videoalpha/07_video_progress_slider.js'), - resource_string(__name__, 'js/src/videoalpha/08_video_volume_control.js'), - resource_string(__name__, 'js/src/videoalpha/09_video_speed_control.js'), - resource_string(__name__, 'js/src/videoalpha/10_video_caption.js'), - resource_string(__name__, 'js/src/videoalpha/11_main.js') + resource_string(__name__, 'js/src/videoalpha/01_initialize.js'), + resource_string(__name__, 'js/src/videoalpha/02_html5_video.js'), + resource_string(__name__, 'js/src/videoalpha/03_video_player.js'), + resource_string(__name__, 'js/src/videoalpha/04_video_control.js'), + resource_string(__name__, 'js/src/videoalpha/05_video_quality_control.js'), + resource_string(__name__, 'js/src/videoalpha/06_video_progress_slider.js'), + resource_string(__name__, 'js/src/videoalpha/07_video_volume_control.js'), + resource_string(__name__, 'js/src/videoalpha/08_video_speed_control.js'), + resource_string(__name__, 'js/src/videoalpha/09_video_caption.js'), + resource_string(__name__, 'js/src/videoalpha/10_main.js') ] } css = {'scss': [resource_string(__name__, 'css/videoalpha/display.scss')]}