diff --git a/common/lib/xmodule/xmodule/js/src/video/02_html5_hls_video.js b/common/lib/xmodule/xmodule/js/src/video/02_html5_hls_video.js index 4df4794043..5331251af1 100644 --- a/common/lib/xmodule/xmodule/js/src/video/02_html5_hls_video.js +++ b/common/lib/xmodule/xmodule/js/src/video/02_html5_hls_video.js @@ -6,8 +6,8 @@ (function(requirejs, require, define) { 'use strict'; - define('video/02_html5_hls_video.js', ['video/02_html5_video.js', 'hls'], - function(HTML5Video, HLS) { + define('video/02_html5_hls_video.js', ['underscore', 'video/02_html5_video.js', 'hls'], + function(_, HTML5Video, HLS) { var HLSVideo = {}; HLSVideo.Player = (function() { @@ -20,20 +20,29 @@ function Player(el, config) { var self = this; + this.config = config; + // do common initialization independent of player type this.init(el, config); + _.bindAll(this, 'playVideo', 'pauseVideo', 'onReady'); + // If we have only HLS sources and browser doesn't support HLS then show error message. if (config.HLSOnlySources && !config.canPlayHLS) { this.showErrorMessage(null, '.video-hls-error'); return; } + this.config.state.el.on('initialize', _.once(function() { + console.log('[HLS Video]: HLS Player initialized'); + self.showPlayButton(); + })); + // Safari has native support to play HLS videos if (config.browserIsSafari) { this.videoEl.attr('src', config.videoSources[0]); } else { - this.hls = new HLS(); + this.hls = new HLS({autoStartLoad: false}); this.hls.loadSource(config.videoSources[0]); this.hls.attachMedia(this.video); @@ -49,6 +58,7 @@ }; }) ); + self.config.onReadyHLS(); }); this.hls.on(HLS.Events.LEVEL_SWITCHED, function(event, data) { var level = self.hls.levels[data.level]; @@ -66,6 +76,31 @@ Player.prototype = Object.create(HTML5Video.Player.prototype); Player.prototype.constructor = Player; + Player.prototype.playVideo = function() { + HTML5Video.Player.prototype.updatePlayerLoadingState.apply(this, ['show']); + if (!this.config.browserIsSafari) { + this.hls.startLoad(); + } + HTML5Video.Player.prototype.playVideo.apply(this); + }; + + Player.prototype.pauseVideo = function() { + HTML5Video.Player.prototype.pauseVideo.apply(this); + HTML5Video.Player.prototype.updatePlayerLoadingState.apply(this, ['hide']); + if (!this.config.browserIsSafari) { + this.hls.stopLoad(); + } + }; + + Player.prototype.onPlaying = function() { + HTML5Video.Player.prototype.onPlaying.apply(this); + HTML5Video.Player.prototype.updatePlayerLoadingState.apply(this, ['hide']); + }; + + Player.prototype.onReady = function() { + this.config.events.onReady(null); + }; + /** * Handler for HLS video errors. This only takes care of fatal erros, non-fatal errors * are automatically handled by hls.js diff --git a/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js b/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js index 367939e21a..9dd82cd071 100644 --- a/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js +++ b/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js @@ -95,6 +95,38 @@ function(_) { this.videoEl.on('error', this.onError.bind(this)); } + Player.prototype.showPlayButton = function() { + this.videoOverlayEl.removeClass('is-hidden'); + }; + + Player.prototype.hidePlayButton = function() { + this.videoOverlayEl.addClass('is-hidden'); + }; + + Player.prototype.showLoading = function() { + this.el + .removeClass('is-initialized') + .find('.spinner') + .removeAttr('tabindex') + .attr({'aria-hidden': 'false'}); + }; + + Player.prototype.hideLoading = function() { + this.el + .addClass('is-initialized') + .find('.spinner') + .attr({'aria-hidden': 'false', tabindex: -1}); + }; + + Player.prototype.updatePlayerLoadingState = function(state) { + if (state === 'show') { + this.hidePlayButton(); + this.showLoading(); + } else if (state === 'hide') { + this.hideLoading(); + } + }; + Player.prototype.callStateChangeCallback = function() { if ($.isFunction(this.config.events.onStateChange)) { this.config.events.onStateChange({ @@ -211,11 +243,15 @@ function(_) { this.videoEl.remove(); }; + Player.prototype.onReady = function() { + this.config.events.onReady(null); + this.showPlayButton(); + }; + Player.prototype.onLoadedMetadata = function() { this.playerState = HTML5Video.PlayerState.PAUSED; if ($.isFunction(this.config.events.onReady)) { - this.config.events.onReady(null); - this.videoOverlayEl.removeClass('is-hidden'); + this.onReady(); } }; @@ -234,7 +270,7 @@ function(_) { Player.prototype.onPause = function() { this.playerState = HTML5Video.PlayerState.PAUSED; this.callStateChangeCallback(); - this.videoOverlayEl.removeClass('is-hidden'); + this.showPlayButton(); }; Player.prototype.onEnded = function() { diff --git a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js index fee8806aa9..8a625723a5 100644 --- a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js +++ b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js @@ -179,6 +179,8 @@ function(HTML5Video, HTML5HLSVideo, Resizer, HLS, _, Time) { state.videoPlayer.player = new HTML5HLSVideo.Player( state.el, _.extend({}, commonPlayerConfig, { + state: state, + onReadyHLS: function() { dfd.resolve(); }, videoSources: state.HLSVideoSources, canPlayHLS: state.canPlayHLS, HLSOnlySources: state.HLSOnlySources diff --git a/common/test/acceptance/tests/video/test_video_module.py b/common/test/acceptance/tests/video/test_video_module.py index 978f124a29..e92e1d789a 100644 --- a/common/test/acceptance/tests/video/test_video_module.py +++ b/common/test/acceptance/tests/video/test_video_module.py @@ -1108,7 +1108,7 @@ class HLSVideoTest(VideoBaseTest): self.navigate_to_video() self.video.click_player_button('play') - self.assertEqual(self.video.state, 'playing') + self.assertIn(self.video.state, ['buffering', 'playing']) self.video.click_player_button('pause') self.assertEqual(self.video.state, 'pause') @@ -1197,6 +1197,7 @@ class HLSVideoTest(VideoBaseTest): Given the course has a Video component with "HLS" video only And I have defined a transcript for the video Then I see the correct text in the captions for transcript + Then I play, pause and seek to 0:00 Then I click on a caption line And video position should be updated accordingly Then I change video position @@ -1209,6 +1210,12 @@ class HLSVideoTest(VideoBaseTest): self.assertIn("Hi, edX welcomes you0.", self.video.captions_text) + # This is required to load the video + self.video.click_player_button('play') + # Below 2 steps are required to test the caption line click scenario + self.video.click_player_button('pause') + self.video.seek('0:00') + for line_no in range(5): self.video.click_transcript_line(line_no=line_no) self.video.wait_for_position('0:0{}'.format(line_no)) diff --git a/package-lock.json b/package-lock.json index 9c0e43ace3..e7dd63273d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5345,9 +5345,13 @@ } }, "hls.js": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.7.2.tgz", - "integrity": "sha1-kLA9FOyQGj9FyAFJLSDDaTRZS5U=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.9.0.tgz", + "integrity": "sha512-YFklk/07BAIEZpI6V1HqjJ4VPOO7zLGcrgzJiiyp9HXK/KH7JDWZylNjT+hn52gfyhqzmagLpJoMXNATgsMBcw==", + "requires": { + "string.prototype.endswith": "0.2.0", + "url-toolkit": "2.1.4" + } }, "hmac-drbg": { "version": "1.0.1", @@ -8955,7 +8959,7 @@ }, "onetime": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true }, @@ -12276,6 +12280,11 @@ "strip-ansi": "3.0.1" } }, + "string.prototype.endswith": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.endswith/-/string.prototype.endswith-0.2.0.tgz", + "integrity": "sha1-oZwg3uUamHd+mkfhDwm+OTubunU=" + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -13811,6 +13820,11 @@ } } }, + "url-toolkit": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.1.4.tgz", + "integrity": "sha512-jAzm/85zNFfkW5Do/37GeGC7uGXBMMawToVdcf5SIpc+x9TmZDrRIUuLRPcvDMfqtt3m8VmDrYhCWh4UbsQLyg==" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index b6b2f8c96a..57cb70ef74 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "extract-text-webpack-plugin": "2.1.2", "file-loader": "1.1.6", "font-awesome": "4.7.0", - "hls.js": "0.7.2", + "hls.js": "0.9.0", "imports-loader": "0.7.1", "jquery": "2.2.4", "jquery-migrate": "1.4.1",