diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js
index c226de12df..68de3e64ad 100644
--- a/cms/static/cms/js/require-config.js
+++ b/cms/static/cms/js/require-config.js
@@ -72,6 +72,7 @@
'ieshim': 'js/src/ie_shim',
'tooltip_manager': 'js/src/tooltip_manager',
'draggabilly': 'js/vendor/draggabilly',
+ 'hls': 'common/js/vendor/hls',
// Files needed for Annotations feature
'annotator': 'js/vendor/ova/annotator-full',
diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss
index e1c7604b94..a99c88a5bc 100644
--- a/common/lib/xmodule/xmodule/css/video/display.scss
+++ b/common/lib/xmodule/xmodule/css/video/display.scss
@@ -250,7 +250,7 @@ $cool-dark: rgb(79, 89, 93); // UXPL cool dark
}
}
- .video-error {
+ .video-error, .video-hls-error {
padding: ($baseline / 5);
background: black;
color: white !important; // the pattern library headings shim is more scoped
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_.m3u8
new file mode 100644
index 0000000000..19a2cf0008
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:11
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:9.576244,
+XXXXXXXXT114-V015600_0_0.ts
+#EXTINF:8.842178,
+XXXXXXXXT114-V015600_0_1.ts
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_0_2.ts
+#EXT-X-ENDLIST
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_0.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_0.ts
new file mode 100644
index 0000000000..9bcd84bffa
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_0.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_1.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_1.ts
new file mode 100644
index 0000000000..e298a596af
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_1.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_2.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_2.ts
new file mode 100644
index 0000000000..fd6d34bf70
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_2.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_.m3u8
new file mode 100644
index 0000000000..e9b49ec7b2
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:11
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:9.576244,
+XXXXXXXXT114-V015600_1_0.ts
+#EXTINF:9.042378,
+XXXXXXXXT114-V015600_1_1.ts
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_1_2.ts
+#EXT-X-ENDLIST
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_0.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_0.ts
new file mode 100644
index 0000000000..780d3209a5
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_0.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_1.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_1.ts
new file mode 100644
index 0000000000..976cda0f8f
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_1.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_2.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_2.ts
new file mode 100644
index 0000000000..bb8f864aea
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_2.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_.m3u8
new file mode 100644
index 0000000000..92aea83871
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:11
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:9.576244,
+XXXXXXXXT114-V015600_2_0.ts
+#EXTINF:9.042378,
+XXXXXXXXT114-V015600_2_1.ts
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_2_2.ts
+#EXT-X-ENDLIST
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_0.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_0.ts
new file mode 100644
index 0000000000..66ef4a9459
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_0.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_1.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_1.ts
new file mode 100644
index 0000000000..96c7730ac1
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_1.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_2.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_2.ts
new file mode 100644
index 0000000000..e6d5d8f631
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_2.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_.m3u8
new file mode 100644
index 0000000000..5918a46217
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:11
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_3_0.ts
+#EXTINF:9.009011,
+XXXXXXXXT114-V015600_3_1.ts
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_3_2.ts
+#EXT-X-ENDLIST
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_0.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_0.ts
new file mode 100644
index 0000000000..a2936b83b0
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_0.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_1.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_1.ts
new file mode 100644
index 0000000000..8ad3c0161a
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_1.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_2.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_2.ts
new file mode 100644
index 0000000000..989bc2675c
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_2.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_.m3u8
new file mode 100644
index 0000000000..307a02d18c
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-TARGETDURATION:11
+#EXT-X-MEDIA-SEQUENCE:0
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_4_0.ts
+#EXTINF:9.009011,
+XXXXXXXXT114-V015600_4_1.ts
+#EXTINF:9.609611,
+XXXXXXXXT114-V015600_4_2.ts
+#EXT-X-ENDLIST
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_0.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_0.ts
new file mode 100644
index 0000000000..e445515c18
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_0.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_1.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_1.ts
new file mode 100644
index 0000000000..ec6d929b02
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_1.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_2.ts b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_2.ts
new file mode 100644
index 0000000000..b338417b42
Binary files /dev/null and b/common/lib/xmodule/xmodule/js/fixtures/hls/XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_2.ts differ
diff --git a/common/lib/xmodule/xmodule/js/fixtures/hls/hls.m3u8 b/common/lib/xmodule/xmodule/js/fixtures/hls/hls.m3u8
new file mode 100644
index 0000000000..cbcfd54645
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/hls/hls.m3u8
@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=264787,RESOLUTION=1280x720
+XXXXXXXXT114-V015600_1_/XXXXXXXXT114-V015600_1_.m3u8
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=328415,RESOLUTION=1920x1080
+XXXXXXXXT114-V015600_0_/XXXXXXXXT114-V015600_0_.m3u8
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=70750,RESOLUTION=640x360
+XXXXXXXXT114-V015600_3_/XXXXXXXXT114-V015600_3_.m3u8
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=148269,RESOLUTION=960x540
+XXXXXXXXT114-V015600_2_/XXXXXXXXT114-V015600_2_.m3u8
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=41276,RESOLUTION=640x360
+XXXXXXXXT114-V015600_4_/XXXXXXXXT114-V015600_4_.m3u8
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_hls.html b/common/lib/xmodule/xmodule/js/fixtures/video_hls.html
new file mode 100644
index 0000000000..e50a49a608
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_hls.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your browser does not support this video format. Try using a different browser.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/lib/xmodule/xmodule/js/karma_xmodule.conf.js b/common/lib/xmodule/xmodule/js/karma_xmodule.conf.js
index 30050b0b8b..2c53cd4da8 100644
--- a/common/lib/xmodule/xmodule/js/karma_xmodule.conf.js
+++ b/common/lib/xmodule/xmodule/js/karma_xmodule.conf.js
@@ -39,6 +39,7 @@ var options = {
{pattern: 'common_static/js/src/utility.js', included: true},
{pattern: 'common_static/js/test/add_ajax_prefix.js', included: true},
{pattern: 'common_static/js/test/i18n.js', included: true},
+ {pattern: 'common_static/common/js/vendor/hls.js', included: true},
{pattern: 'public/js/split_test_staff.js', included: true},
{pattern: 'src/word_cloud/d3.min.js', included: true},
@@ -77,7 +78,8 @@ var options = {
],
fixtureFiles: [
- {pattern: 'fixtures/*.*'}
+ {pattern: 'fixtures/*.*'},
+ {pattern: 'fixtures/hls/**/*.*'}
],
runFiles: [
diff --git a/common/lib/xmodule/xmodule/js/spec/helper.js b/common/lib/xmodule/xmodule/js/spec/helper.js
index d1fbb153dc..6c3462f8be 100644
--- a/common/lib/xmodule/xmodule/js/spec/helper.js
+++ b/common/lib/xmodule/xmodule/js/spec/helper.js
@@ -264,8 +264,17 @@
return state;
};
+ jasmine.initializeHLSPlayer = function(params) {
+ return jasmine.initializePlayer('video_hls.html', params);
+ };
+
jasmine.initializePlayerYouTube = function(params) {
// "video.html" contains HTML template for a YouTube video.
return jasmine.initializePlayer('video.html', params);
};
+
+ jasmine.DescribeInfo = function(description, specDefinitions) {
+ this.description = description;
+ this.specDefinitions = specDefinitions;
+ };
}).call(this);
diff --git a/common/lib/xmodule/xmodule/js/spec/main_requirejs.js b/common/lib/xmodule/xmodule/js/spec/main_requirejs.js
index 1f71824335..4b04278962 100644
--- a/common/lib/xmodule/xmodule/js/spec/main_requirejs.js
+++ b/common/lib/xmodule/xmodule/js/spec/main_requirejs.js
@@ -35,11 +35,17 @@
baseUrl: '/base/',
paths: {
moment: 'common_static/common/js/vendor/moment-with-locales',
- 'draggabilly': 'common_static/js/vendor/draggabilly',
- 'edx-ui-toolkit': 'common_static/edx-ui-toolkit'
+ draggabilly: 'common_static/js/vendor/draggabilly',
+ 'edx-ui-toolkit': 'common_static/edx-ui-toolkit',
+ hls: 'common_static/common/js/vendor/hls'
},
- 'moment': {
- exports: 'moment'
+ shim: {
+ moment: {
+ exports: 'moment'
+ },
+ hls: {
+ exports: 'Hls'
+ }
}
});
}).call(this, RequireJS.requirejs, RequireJS.define);
diff --git a/common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js b/common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
index 3cb155df53..5f23f4338b 100644
--- a/common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
@@ -1,7 +1,10 @@
(function(undefined) {
describe('Video HTML5Video', function() {
var STATUS = window.STATUS;
- var state, oldOTBD, playbackRates = [0.75, 1.0, 1.25, 1.5];
+ var state,
+ oldOTBD,
+ playbackRates = [0.75, 1.0, 1.25, 1.5],
+ describeInfo;
beforeEach(function() {
oldOTBD = window.onTouchBasedDevice;
@@ -17,10 +20,8 @@
window.onTouchBasedDevice = oldOTBD;
});
- describe('on non-Touch devices', function() {
+ describeInfo = new jasmine.DescribeInfo('on non-Touch devices ', function() {
beforeEach(function() {
- state = jasmine.initializePlayer('video_html5.html');
-
state.videoPlayer.player.config.events.onReady = jasmine.createSpy('onReady');
});
@@ -321,6 +322,22 @@
});
});
+ describe('non-hls encoding', function() {
+ beforeEach(function(done) {
+ state = jasmine.initializePlayer('video_html5.html');
+ done();
+ });
+ jasmine.getEnv().describe(describeInfo.description, describeInfo.specDefinitions);
+ });
+
+ describe('hls encoding', function() {
+ beforeEach(function(done) {
+ state = jasmine.initializeHLSPlayer();
+ done();
+ });
+ jasmine.getEnv().describe(describeInfo.description, describeInfo.specDefinitions);
+ });
+
it('native controls are used on iPhone', function() {
window.onTouchBasedDevice.and.returnValue(['iPhone']);
diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_events_plugin_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_events_plugin_spec.js
index 98722843a9..477328ab8c 100644
--- a/common/lib/xmodule/xmodule/js/spec/video/video_events_plugin_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/video/video_events_plugin_spec.js
@@ -1,15 +1,11 @@
(function(undefined) {
'use strict';
- describe('VideoPlayer Events plugin', function() {
- var state, oldOTBD, Logger = window.Logger;
+ var describeInfo, state, oldOTBD;
+
+ describeInfo = new jasmine.DescribeInfo('', function() {
+ var Logger = window.Logger;
beforeEach(function() {
- oldOTBD = window.onTouchBasedDevice;
- window.onTouchBasedDevice = jasmine
- .createSpy('onTouchBasedDevice')
- .and.returnValue(null);
-
- state = jasmine.initializePlayer();
spyOn(Logger, 'log');
spyOn(state.videoEventsPlugin, 'getCurrentTime').and.returnValue(10);
});
@@ -27,7 +23,7 @@
state.el.trigger('ready');
expect(Logger.log).toHaveBeenCalledWith('load_video', {
id: 'id',
- code: 'html5'
+ code: this.code
});
});
@@ -36,7 +32,7 @@
state.el.trigger('play');
expect(Logger.log).toHaveBeenCalledWith('play_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeFalsy();
@@ -52,7 +48,7 @@
state.el.trigger('pause');
expect(Logger.log).toHaveBeenCalledWith('pause_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
@@ -62,7 +58,7 @@
state.el.trigger('speedchange', ['2.0', '1.0']);
expect(Logger.log).toHaveBeenCalledWith('speed_change_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
current_time: 10,
old_speed: '1.0',
new_speed: '2.0'
@@ -73,7 +69,7 @@
state.el.trigger('seek', [1, 0, 'any']);
expect(Logger.log).toHaveBeenCalledWith('seek_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
old_time: 0,
new_time: 1,
type: 'any'
@@ -91,7 +87,7 @@
state.el.trigger('ended');
expect(Logger.log).toHaveBeenCalledWith('stop_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
@@ -100,7 +96,7 @@
state.el.trigger('stop');
expect(Logger.log).toHaveBeenCalledWith('stop_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
@@ -110,7 +106,7 @@
state.el.trigger('skip', [false]);
expect(Logger.log).toHaveBeenCalledWith('skip_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
});
@@ -119,7 +115,7 @@
state.el.trigger('skip', [true]);
expect(Logger.log).toHaveBeenCalledWith('do_not_show_again_video', {
id: 'id',
- code: 'html5',
+ code: this.code,
currentTime: 10
});
});
@@ -128,7 +124,7 @@
state.el.trigger('language_menu:show');
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.shown', {
id: 'id',
- code: 'html5'
+ code: this.code
});
});
@@ -136,7 +132,7 @@
state.el.trigger('language_menu:hide');
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.hidden', {
id: 'id',
- code: 'html5',
+ code: this.code,
language: 'en'
});
});
@@ -145,7 +141,7 @@
state.el.trigger('transcript:show');
expect(Logger.log).toHaveBeenCalledWith('show_transcript', {
id: 'id',
- code: 'html5',
+ code: this.code,
current_time: 10
});
});
@@ -154,7 +150,7 @@
state.el.trigger('transcript:hide');
expect(Logger.log).toHaveBeenCalledWith('hide_transcript', {
id: 'id',
- code: 'html5',
+ code: this.code,
current_time: 10
});
});
@@ -163,7 +159,7 @@
state.el.trigger('captions:show');
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.shown', {
id: 'id',
- code: 'html5',
+ code: this.code,
current_time: 10
});
});
@@ -172,7 +168,7 @@
state.el.trigger('captions:hide');
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.hidden', {
id: 'id',
- code: 'html5',
+ code: this.code,
current_time: 10
});
});
@@ -200,4 +196,31 @@
});
});
});
+
+ describe('VideoPlayer Events plugin', function() {
+ beforeEach(function() {
+ oldOTBD = window.onTouchBasedDevice;
+ window.onTouchBasedDevice = jasmine
+ .createSpy('onTouchBasedDevice')
+ .and.returnValue(null);
+ });
+
+ describe('html5 encoding only', function() {
+ beforeEach(function(done) {
+ this.code = 'html5';
+ state = jasmine.initializePlayer('video_html5.html');
+ done();
+ });
+ jasmine.getEnv().describe(describeInfo.description, describeInfo.specDefinitions);
+ });
+
+ describe('hls encoding', function() {
+ beforeEach(function(done) {
+ this.code = 'hls';
+ state = jasmine.initializeHLSPlayer();
+ done();
+ });
+ jasmine.getEnv().describe(describeInfo.description, describeInfo.specDefinitions);
+ });
+ });
}).call(this);
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 b2faa75a89..8a34019f36 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
@@ -2,8 +2,8 @@
'use strict';
require(
-['video/03_video_player.js'],
-function(VideoPlayer) {
+['video/03_video_player.js', 'hls'],
+function(VideoPlayer, HLS) {
describe('VideoPlayer', function() {
var state, oldOTBD, empty_arguments;
@@ -969,6 +969,48 @@ function(VideoPlayer) {
expect(state.videoPlayer.player.setPlaybackRate).toHaveBeenCalledWith('1.0');
});
});
+
+ describe('HLS Video', function() {
+ beforeEach(function() {
+ state = jasmine.initializeHLSPlayer();
+ });
+
+ it('does not show error message if hls is supported', function() {
+ expect($('.video-hls-error')).toHaveClass('is-hidden');
+ });
+
+ it('can extract hls video sources correctly', function() {
+ expect(state.HLSVideoSources).toEqual(['/base/fixtures/hls/hls.m3u8']);
+ expect(state.videoPlayer.player.hls).toBeDefined();
+ });
+
+ describe('on safari', function() {
+ beforeEach(function() {
+ spyOn(HLS, 'isSupported').and.returnValue(false);
+ state = jasmine.initializeHLSPlayer();
+ state.canPlayHLS = true;
+ state.browserIsSafari = true;
+ });
+
+ it('can use native hls playback support', function() {
+ expect(state.videoPlayer.player.hls).toBeUndefined();
+ });
+ });
+ });
+
+ describe('HLS Video Errors', function() {
+ beforeEach(function() {
+ spyOn(HLS, 'isSupported').and.returnValue(false);
+ state = jasmine.initializeHLSPlayer({sources: ['/base/fixtures/hls/hls.m3u8']});
+ });
+
+ it('shows error message if hls is not supported', function() {
+ expect($('.video-hls-error')).not.toHaveClass('is-hidden');
+ expect($('.video-hls-error').text().trim()).toEqual(
+ 'Your browser does not support this video format. Try using a different browser.'
+ );
+ });
+ });
});
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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 c9e385ab9d..1082a811bc 100644
--- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
@@ -1,4 +1,4 @@
-/* eslint no-console:0 */
+/* eslint-disable no-console, no-param-reassign */
/**
* @file Initialize module works with the JSON config, and sets up various
* settings, parameters, variables. After all setup actions are performed, it
@@ -278,6 +278,18 @@ function(VideoPlayer, i18n, moment, _) {
return false;
}
+ /**
+ * Extract HLS video URLs from available video URLs.
+ *
+ * @param {object} state The object contaning the state (properties, methods, modules) of the Video player.
+ * @returns Array of available HLS video source urls.
+ */
+ function extractHLSVideoSources(state) {
+ return _.filter(state.config.sources, function(source) {
+ return /\.m3u8$/.test(source);
+ });
+ }
+
// function _prepareHTML5Video(state)
// The function prepare HTML5 video, parse HTML5
// video sources etc.
@@ -325,6 +337,7 @@ function(VideoPlayer, i18n, moment, _) {
state.controlHideTimeout = null;
state.captionState = 'invisible';
state.captionHideTimeout = null;
+ state.HLSVideoSources = extractHLSVideoSources(state);
}
function _initializeModules(state, i18n) {
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
new file mode 100644
index 0000000000..4df4794043
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/video/02_html5_hls_video.js
@@ -0,0 +1,108 @@
+/* eslint-disable no-console, no-param-reassign */
+/**
+ * HTML5 video player module to support HLS video playback.
+ *
+ */
+
+(function(requirejs, require, define) {
+ 'use strict';
+ define('video/02_html5_hls_video.js', ['video/02_html5_video.js', 'hls'],
+ function(HTML5Video, HLS) {
+ var HLSVideo = {};
+
+ HLSVideo.Player = (function() {
+ /**
+ * Initialize HLS video player.
+ *
+ * @param {jQuery} el Reference to video player container element
+ * @param {Object} config Contains common config for video player
+ */
+ function Player(el, config) {
+ var self = this;
+
+ // do common initialization independent of player type
+ this.init(el, config);
+
+ // 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;
+ }
+
+ // 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.loadSource(config.videoSources[0]);
+ this.hls.attachMedia(this.video);
+
+ this.hls.on(HLS.Events.ERROR, this.onError.bind(this));
+
+ this.hls.on(HLS.Events.MANIFEST_PARSED, function(event, data) {
+ console.log(
+ '[HLS Video]: MANIFEST_PARSED, qualityLevelsInfo: ',
+ data.levels.map(function(level) {
+ return {
+ bitrate: level.bitrate,
+ resolution: level.width + 'x' + level.height
+ };
+ })
+ );
+ });
+ this.hls.on(HLS.Events.LEVEL_SWITCHED, function(event, data) {
+ var level = self.hls.levels[data.level];
+ console.log(
+ '[HLS Video]: LEVEL_SWITCHED, qualityLevelInfo: ',
+ {
+ bitrate: level.bitrate,
+ resolution: level.width + 'x' + level.height
+ }
+ );
+ });
+ }
+ }
+
+ Player.prototype = Object.create(HTML5Video.Player.prototype);
+ Player.prototype.constructor = Player;
+
+ /**
+ * Handler for HLS video errors. This only takes care of fatal erros, non-fatal errors
+ * are automatically handled by hls.js
+ *
+ * @param {String} event `hlsError`
+ * @param {Object} data Contains the information regarding error occurred.
+ */
+ Player.prototype.onError = function(event, data) {
+ if (data.fatal) {
+ switch (data.type) {
+ case HLS.ErrorTypes.NETWORK_ERROR:
+ console.error(
+ '[HLS Video]: Fatal network error encountered, try to recover. Details: %s',
+ data.details
+ );
+ this.hls.startLoad();
+ break;
+ case HLS.ErrorTypes.MEDIA_ERROR:
+ console.error(
+ '[HLS Video]: Fatal media error encountered, try to recover. Details: %s',
+ data.details
+ );
+ this.hls.recoverMediaError();
+ break;
+ default:
+ console.error(
+ '[HLS Video]: Unrecoverable error encountered. Details: %s',
+ data.details
+ );
+ break;
+ }
+ }
+ };
+
+ return Player;
+ }());
+
+ return HLSVideo;
+ });
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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 93759ee468..d9db98a596 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
@@ -1,3 +1,4 @@
+/* eslint-disable no-console, no-param-reassign */
/**
* @file HTML5 video player module. Provides methods to control the in-browser
* HTML5 video player.
@@ -16,11 +17,94 @@
(function(requirejs, require, define) {
define(
'video/02_html5_video.js',
-[],
-function() {
+['underscore'],
+function(_) {
var HTML5Video = {};
HTML5Video.Player = (function() {
+ /*
+ * Constructor function for HTML5 Video player.
+ *
+ * @param {String|Object} el A DOM element where the HTML5 player will
+ * be inserted (as returned by jQuery(selector) function), or a
+ * selector string which will be used to select an element. This is a
+ * required parameter.
+ *
+ * @param config - An object whose properties will be used as
+ * configuration options for the HTML5 video player. This is an
+ * optional parameter. In the case if this parameter is missing, or
+ * some of the config object's properties are missing, defaults will be
+ * used. The available options (and their defaults) are as
+ * follows:
+ *
+ * config = {
+ *
+ * videoSources: [], // An array with properties being video
+ * // sources. The property name is the
+ * // video format of the source. Supported
+ * // video formats are: 'mp4', 'webm', and
+ * // 'ogg'.
+ *
+ * events: { // Object's properties identify the
+ * // events that the API fires, and the
+ * // functions (event listeners) that the
+ * // API will call when those events occur.
+ * // If value is null, or property is not
+ * // specified, then no callback will be
+ * // called for that event.
+ *
+ * onReady: null,
+ * onStateChange: null
+ * }
+ * }
+ */
+ function Player(el, config) {
+ var errorMessage, lastSource, sourceList;
+
+ // A simple test to see that the 'config' is a normal object.
+ if ($.isPlainObject(config) === false) {
+ return;
+ }
+
+ // We should have at least one video source. Otherwise there is no
+ // point to continue.
+ if (!config.videoSources && !config.videoSources.length) {
+ return;
+ }
+
+ // Create HTML markup for individual sources of the HTML5