diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
index c199efd959..3d697b0e44 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
@@ -4,7 +4,7 @@
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 477328ab8c..938c2a3bee 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
@@ -23,7 +23,8 @@
state.el.trigger('ready');
expect(Logger.log).toHaveBeenCalledWith('load_video', {
id: 'id',
- code: this.code
+ code: this.code,
+ duration: this.duration
});
});
@@ -33,7 +34,8 @@
expect(Logger.log).toHaveBeenCalledWith('play_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeFalsy();
});
@@ -49,7 +51,8 @@
expect(Logger.log).toHaveBeenCalledWith('pause_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
});
@@ -61,7 +64,8 @@
code: this.code,
current_time: 10,
old_speed: '1.0',
- new_speed: '2.0'
+ new_speed: '2.0',
+ duration: this.duration
});
});
@@ -72,7 +76,8 @@
code: this.code,
old_time: 0,
new_time: 1,
- type: 'any'
+ type: 'any',
+ duration: this.duration
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
});
@@ -88,7 +93,8 @@
expect(Logger.log).toHaveBeenCalledWith('stop_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
@@ -97,7 +103,8 @@
expect(Logger.log).toHaveBeenCalledWith('stop_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
expect(state.videoEventsPlugin.emitPlayVideoEvent).toBeTruthy();
});
@@ -107,7 +114,8 @@
expect(Logger.log).toHaveBeenCalledWith('skip_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
});
@@ -116,7 +124,8 @@
expect(Logger.log).toHaveBeenCalledWith('do_not_show_again_video', {
id: 'id',
code: this.code,
- currentTime: 10
+ currentTime: 10,
+ duration: this.duration
});
});
@@ -124,7 +133,8 @@
state.el.trigger('language_menu:show');
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.shown', {
id: 'id',
- code: this.code
+ code: this.code,
+ duration: this.duration
});
});
@@ -133,7 +143,8 @@
expect(Logger.log).toHaveBeenCalledWith('edx.video.language_menu.hidden', {
id: 'id',
code: this.code,
- language: 'en'
+ language: 'en',
+ duration: this.duration
});
});
@@ -142,7 +153,8 @@
expect(Logger.log).toHaveBeenCalledWith('show_transcript', {
id: 'id',
code: this.code,
- current_time: 10
+ current_time: 10,
+ duration: this.duration
});
});
@@ -151,7 +163,8 @@
expect(Logger.log).toHaveBeenCalledWith('hide_transcript', {
id: 'id',
code: this.code,
- current_time: 10
+ current_time: 10,
+ duration: this.duration
});
});
@@ -160,7 +173,8 @@
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.shown', {
id: 'id',
code: this.code,
- current_time: 10
+ current_time: 10,
+ duration: this.duration
});
});
@@ -169,7 +183,8 @@
expect(Logger.log).toHaveBeenCalledWith('edx.video.closed_captions.hidden', {
id: 'id',
code: this.code,
- current_time: 10
+ current_time: 10,
+ duration: this.duration
});
});
@@ -208,6 +223,7 @@
describe('html5 encoding only', function() {
beforeEach(function(done) {
this.code = 'html5';
+ this.duration = 111;
state = jasmine.initializePlayer('video_html5.html');
done();
});
@@ -217,6 +233,7 @@
describe('hls encoding', function() {
beforeEach(function(done) {
this.code = 'hls';
+ this.duration = 111;
state = jasmine.initializeHLSPlayer();
done();
});
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 1c61ba6381..8950b5bcce 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
@@ -1011,6 +1011,21 @@ function(VideoPlayer, HLS) {
});
});
+ describe('Video duration', function() {
+ beforeEach(function() {
+ state = jasmine.initializePlayer();
+ spyOn(state.videoPlayer, 'duration').and.returnValue(61);
+ });
+
+ it('overrides the duration if not set', function(done) {
+ jasmine.waitUntil(function() {
+ return state.duration !== undefined;
+ }).then(function() {
+ expect(state.duration).toEqual(61);
+ }).always(done);
+ });
+ });
+
describe('Overlay Play Button', function() {
var playButtonOverlaySelector = '.video-wrapper .btn-play.fa.fa-youtube-play.fa-2x';
beforeEach(function() {
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 f99b13f634..825976e808 100644
--- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
@@ -569,6 +569,7 @@ function(VideoPlayer, i18n, moment, _) {
this.config.speed || this.config.generalSpeed
);
this.htmlPlayerLoaded = false;
+ this.duration = this.metadata.duration;
_setConfigurations(this);
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 82bd43bc52..f9b327ea38 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
@@ -653,6 +653,9 @@ function(HTML5Video, HTML5HLSVideo, Resizer, HLS, _) {
var duration = this.videoPlayer.duration(),
time = this.videoPlayer.figureOutStartingTime(duration);
+ // this.duration will be set initially only if duration is coming from edx-val
+ this.duration = this.duration || duration;
+
if (time > 0 && this.videoPlayer.goToStartTime) {
this.videoPlayer.seekTo(time);
}
diff --git a/common/lib/xmodule/xmodule/js/src/video/09_events_plugin.js b/common/lib/xmodule/xmodule/js/src/video/09_events_plugin.js
index b00a19f4d1..c07e91b81c 100644
--- a/common/lib/xmodule/xmodule/js/src/video/09_events_plugin.js
+++ b/common/lib/xmodule/xmodule/js/src/video/09_events_plugin.js
@@ -143,7 +143,8 @@
var logInfo = _.extend({
id: this.state.id,
// eslint-disable-next-line no-nested-ternary
- code: this.state.isYoutubeType() ? this.state.youtubeId() : this.state.canPlayHLS ? 'hls' : 'html5'
+ code: this.state.isYoutubeType() ? this.state.youtubeId() : this.state.canPlayHLS ? 'hls' : 'html5',
+ duration: this.state.duration
}, data, this.options.data);
Logger.log(eventName, logInfo);
}
diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py
index a6d6d4a7a1..50dcb950cd 100644
--- a/common/lib/xmodule/xmodule/video_module/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module/video_module.py
@@ -211,6 +211,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
download_video_link = None
branding_info = None
youtube_streams = ""
+ video_duration = None
# Determine if there is an alternative source for this video
# based on user locale. This exists to support cases where
@@ -253,7 +254,11 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
if val_video_urls["youtube"]:
youtube_streams = "1.00:{}".format(val_video_urls["youtube"])
- except edxval_api.ValInternalError:
+ # get video duration
+ video_data = edxval_api.get_video_info(self.edx_video_id.strip())
+ video_duration = video_data.get('duration')
+
+ except (edxval_api.ValInternalError, edxval_api.ValVideoNotFoundError):
# VAL raises this exception if it can't find data for the edx video ID. This can happen if the
# course data is ported to a machine that does not have the VAL data. So for now, pass on this
# exception and fallback to whatever we find in the VideoDescriptor.
@@ -326,6 +331,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
'sub': self.sub,
'sources': sources,
'poster': poster,
+ 'duration': video_duration,
# This won't work when we move to data that
# isn't on the filesystem
'captionDataDir': getattr(self, 'data_dir', None),
diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py
index e248519696..b940ddc9c8 100644
--- a/lms/djangoapps/courseware/tests/test_video_mongo.py
+++ b/lms/djangoapps/courseware/tests/test_video_mongo.py
@@ -69,6 +69,7 @@ class TestVideoYouTube(TestVideo):
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
'sub': 'a_sub_file.srt.sjson',
'sources': sources,
+ 'duration': None,
'poster': None,
'captionDataDir': None,
'showCaptions': 'true',
@@ -149,6 +150,7 @@ class TestVideoNonYouTube(TestVideo):
'streams': '1.00:3_yD_cEKoCk',
'sub': 'a_sub_file.srt.sjson',
'sources': sources,
+ 'duration': None,
'poster': None,
'captionDataDir': None,
'showCaptions': 'true',
@@ -206,6 +208,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
'streams': '1.00:3_yD_cEKoCk',
'sub': 'a_sub_file.srt.sjson',
'sources': '[]',
+ 'duration': 111.0,
'poster': None,
'captionDataDir': None,
'showCaptions': 'true',
@@ -306,6 +309,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
for data in cases:
metadata = self.default_metadata_dict
metadata['sources'] = sources
+ metadata['duration'] = None
DATA = SOURCE_XML.format(
download_track=data['download_track'],
track=data['track'],
@@ -424,6 +428,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
],
'poster': 'null',
}
+ initial_context['metadata']['duration'] = None
for data in cases:
DATA = SOURCE_XML.format(
@@ -674,7 +679,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
result = create_video(
dict(
client_video_id='A Client Video id',
- duration=111,
+ duration=111.0,
edx_video_id=edx_video_id,
status='test',
encoded_videos=encoded_videos,
@@ -835,6 +840,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
],
'poster': 'null',
}
+ initial_context['metadata']['duration'] = None
for data in cases:
DATA = SOURCE_XML.format(
@@ -1542,7 +1548,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
create_video({
'edx_video_id': self.descriptor.edx_video_id,
'client_video_id': 'test_client_video_id',
- 'duration': 111,
+ 'duration': 111.0,
'status': 'dummy',
'encoded_videos': [{
'profile': 'mobile',
@@ -1643,7 +1649,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
self.assertEqual(video.edx_video_id, 'test_edx_video_id')
video_data = get_video_info(video.edx_video_id)
self.assertEqual(video_data['client_video_id'], 'test_client_video_id')
- self.assertEqual(video_data['duration'], 111)
+ self.assertEqual(video_data['duration'], 111.0)
self.assertEqual(video_data['status'], 'imported')
self.assertEqual(video_data['courses'], [{id_generator.target_course_id: None}])
self.assertEqual(video_data['encoded_videos'][0]['profile'], 'mobile')
@@ -1788,6 +1794,7 @@ class TestVideoWithBumper(TestVideo):
'sub': 'a_sub_file.srt.sjson',
'sources': sources,
'poster': None,
+ 'duration': None,
'captionDataDir': None,
'showCaptions': 'true',
'generalSpeed': 1.0,