Commit 5917ed06 by ataki

hard start/stop times to video player

This pull request makes the video player module respect
the video start/stop times specified by the instructor
as a hard start/stops, i.e. the video treats the start
time as the beginning of the video and the stop time as
the end.

Previously, the video module displayed the full
video and displayed a jquery ui slider range representing
the start and end times.

This new functionality invalidates two previous acceptance
tests for the video module:

    - slider visible test
    - finish time video acceptance test.

These tests are removed from the common acceptance tests.
parent d3bb7225
......@@ -5,6 +5,8 @@ function (Sjson) {
describe('Sjson', function () {
var data = jasmine.stubbedCaption,
sjson;
var videoStops = [0, 3120, 6270, 8490, 21620, 24920];
var OUT_OF_BOUNDS_STOP = 10024920;
beforeEach(function() {
sjson = new Sjson(data);
......@@ -23,12 +25,42 @@ function (Sjson) {
});
it('search returns a correct caption index', function () {
expect(sjson.search(0)).toEqual(-1);
expect(sjson.search(3120)).toEqual(1);
expect(sjson.search(6270)).toEqual(2);
expect(sjson.search(8490)).toEqual(2);
expect(sjson.search(21620)).toEqual(4);
expect(sjson.search(24920)).toEqual(5);
expect(sjson.search(videoStops[0])).toEqual(0);
expect(sjson.search(videoStops[1])).toEqual(1);
expect(sjson.search(videoStops[2])).toEqual(2);
expect(sjson.search(videoStops[3])).toEqual(2);
expect(sjson.search(videoStops[4])).toEqual(4);
expect(sjson.search(videoStops[5])).toEqual(5);
});
it('search returns the last entry for a value outside the bounds of the array', function() {
expect(sjson.search(OUT_OF_BOUNDS_STOP)).toEqual(sjson.getCaptions().length - 1);
});
it('search returns the first entry for a negative index in the array', function() {
expect(sjson.search(-1)).toEqual(0);
});
it('search only searches through a subrange of times if start / end times are specified', function () {
var start = videoStops[2] - 100;
var end = videoStops[5] - 100;
var results = sjson.filter(start, end);
var expectedLength = results.captions.length - 1;
expect(sjson.search(videoStops[0], start, end)).toEqual(0);
expect(sjson.search(videoStops[1], start, end)).toEqual(0);
expect(sjson.search(videoStops[2], start, end)).toEqual(0);
expect(sjson.search(videoStops[3], start, end)).toEqual(0);
expect(sjson.search(OUT_OF_BOUNDS_STOP, start, end)).toEqual(expectedLength);
});
it('filters results correctly given a start and end time', function () {
var start = videoStops[1] - 100;
var end = videoStops[4] - 100;
var results = sjson.filter(start, end);
expect(results.start.length).toEqual(3);
expect(results.captions.length).toEqual(3);
});
});
});
......
......@@ -799,6 +799,10 @@
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(5);
// To test speed, don't use start / end times.
state.config.startTime = 0;
state.config.endTime = null;
// Flash mode
state.speed = '2.0';
spyOn(state, 'isFlashMode').andReturn(true);
......
......@@ -278,7 +278,8 @@
describe('constructor with end-time', function () {
it(
'saved position is 0, timer slider and VCR set to 0:00',
'saved position is 0, timer slider and VCR set to 0:00 ' +
'and ending at specified end-time',
function ()
{
var duration, sliderEl, expectedValue;
......@@ -301,7 +302,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:00 / 1:00');
expect(expectedValue).toHaveText('0:00 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(0);
......@@ -335,7 +336,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:15 / 1:00');
expect(expectedValue).toHaveText('0:15 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(15);
......@@ -369,7 +370,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:00 / 1:00');
expect(expectedValue).toHaveText('0:00 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(0);
......@@ -403,7 +404,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:00 / 1:00');
expect(expectedValue).toHaveText('0:00 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(0);
......@@ -438,7 +439,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:00 / 1:00');
expect(expectedValue).toHaveText('0:00 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(0);
......@@ -450,7 +451,7 @@
describe('constructor with start-time and end-time', function () {
it(
'saved position is 0, timer slider and VCR set to start-time',
'saved position is 0, timer slider and VCR set to appropriate start and end times',
function ()
{
var duration, sliderEl, expectedValue;
......@@ -474,7 +475,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:10 / 1:00');
expect(expectedValue).toHaveText('0:10 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(10);
......@@ -509,7 +510,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:15 / 1:00');
expect(expectedValue).toHaveText('0:15 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(15);
......@@ -544,7 +545,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:10 / 1:00');
expect(expectedValue).toHaveText('0:10 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(10);
......@@ -579,7 +580,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:10 / 1:00');
expect(expectedValue).toHaveText('0:10 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(10);
......@@ -614,7 +615,7 @@
runs(function () {
expectedValue = $('.video-controls').find('.vidtime');
expect(expectedValue).toHaveText('0:10 / 1:00');
expect(expectedValue).toHaveText('0:10 / 0:20');
expectedValue = sliderEl.slider('option', 'value');
expect(expectedValue).toBe(10);
......
......@@ -26,6 +26,8 @@
expect(state.videoProgressSlider.slider).toBe('.slider');
expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
min: 0,
max: null,
change: state.videoProgressSlider.onChange,
slide: state.videoProgressSlider.onSlide,
stop: state.videoProgressSlider.onStop
......
......@@ -27,14 +27,24 @@ function() {
return sjson.text.length;
};
var search = function (time) {
function search(time, startTime, endTime) {
var start = getStartTimes(),
max = size() - 1,
min = 0,
results,
index;
if (time < start[min]) {
return -1;
// if we specify a start and end time to search,
// search the filtered list of captions in between
// the start / end times.
// Else, search the unfiltered list.
if (typeof startTime !== 'undefined' &&
typeof endTime !== 'undefined') {
results = filter(startTime, endTime);
start = results.start;
max = results.captions.length - 1;
} else {
start = getStartTimes();
}
while (min < max) {
index = Math.ceil((max + min) / 2);
......@@ -51,10 +61,44 @@ function() {
return min;
};
function filter(start, end) {
var filteredTimes = [];
var filteredCaptions = [];
var startTimes = getStartTimes();
var captions = getCaptions();
var currentStartTime;
var i;
if (startTimes.length !== captions.length) {
return [];
}
// if end is null, then it's been set to
// some erroneous value, so filter using the
// entire array as long as it's not empty
if (end === null && startTimes.length) {
end = startTimes[startTimes.length - 1];
}
for (i = 0; i < startTimes.length; i++) {
currentStartTime = startTimes[i];
if (currentStartTime >= start && currentStartTime <= end) {
filteredTimes.push(currentStartTime);
filteredCaptions.push(captions[i]);
}
}
return {
'start': filteredTimes,
'captions': filteredCaptions
};
}
return {
getCaptions: getCaptions,
getStartTimes: getStartTimes,
getSize: size,
filter: filter,
search: search
};
};
......
......@@ -320,7 +320,6 @@ function (HTML5Video, Resizer) {
// When the video will start playing again from the start, the
// start-time and end-time will come back into effect.
this.videoPlayer.goToStartTime = true;
this.videoPlayer.stopAtEndTime = true;
}
this.videoPlayer.player.playVideo();
......@@ -340,11 +339,9 @@ function (HTML5Video, Resizer) {
// than end-time. Also, we must make sure that this is only done
// once per video playing from start to end.
if (
this.videoPlayer.stopAtEndTime &&
this.videoPlayer.endTime !== null &&
this.videoPlayer.endTime <= this.videoPlayer.currentTime
) {
this.videoPlayer.stopAtEndTime = false;
this.videoPlayer.pause();
......@@ -463,9 +460,6 @@ function (HTML5Video, Resizer) {
// After the user seeks, the video will start playing from
// the sought point, and stop playing at the end.
this.videoPlayer.goToStartTime = false;
if (time > this.videoPlayer.endTime || this.videoPlayer.endTime === null) {
this.videoPlayer.stopAtEndTime = false;
}
this.videoPlayer.seekTo(time);
this.videoPlayer.log(
......@@ -769,7 +763,6 @@ function (HTML5Video, Resizer) {
videoPlayer.endTime <= videoPlayer.startTime ||
videoPlayer.endTime >= duration
) {
videoPlayer.stopAtEndTime = false;
videoPlayer.endTime = null;
} else if (this.isFlashMode()) {
videoPlayer.endTime /= Number(this.speed);
......@@ -825,6 +818,10 @@ function (HTML5Video, Resizer) {
duration = this.videoPlayer.duration(),
youTubeId;
if (this.config.endTime !== null) {
duration = Math.min(this.config.endTime, duration);
}
this.trigger(
'videoProgressSlider.updatePlayTime',
{
......
......@@ -296,7 +296,10 @@ function () {
}
function updateVcrVidTime(params) {
this.videoControl.vidTimeEl.html(Time.format(params.time) + ' / ' + Time.format(params.duration));
var duration = (this.config.endTime !== null) ? this.config.endTime : params.duration;
// in case endTime is accidentally specified as being greater than the video
duration = Math.min(duration, params.duration);
this.videoControl.vidTimeEl.html(Time.format(params.time) + ' / ' + Time.format(duration));
}
});
......
......@@ -94,6 +94,8 @@ function () {
this.videoProgressSlider.slider = this.videoProgressSlider.el
.slider({
range: 'min',
min: this.config.startTime,
max: this.config.endTime,
slide: this.videoProgressSlider.onSlide,
stop: this.videoProgressSlider.onStop
});
......@@ -147,25 +149,6 @@ function () {
// with actual starting and ending point of the video.
rangeParams = getRangeParams(start, end, duration);
if (!this.videoProgressSlider.sliderRange) {
this.videoProgressSlider.sliderRange = $('<div />', {
'class': 'ui-slider-range ' +
'ui-widget-header ' +
'ui-corner-all ' +
'slider-range'
})
.css({
left: rangeParams.left,
width: rangeParams.width
});
this.videoProgressSlider.sliderProgress
.after(this.videoProgressSlider.sliderRange);
} else {
this.videoProgressSlider.sliderRange
.css(rangeParams);
}
}
function getRangeParams(startTime, endTime, duration) {
......@@ -183,6 +166,10 @@ function () {
var time = ui.value,
duration = this.videoPlayer.duration();
if (this.config.endTime !== null) {
duration = Math.min(this.config.endTime, duration);
}
this.videoProgressSlider.frozen = true;
// Remember the seek to value so that we don't repeat ourselves on the
......@@ -235,8 +222,15 @@ function () {
}
function updatePlayTime(params) {
var time = Math.floor(params.time),
duration = Math.floor(params.duration);
var time = Math.floor(params.time);
// params.duration could accidentally be construed as a floating
// point double. Since we're displaying this number, round down
// to nearest second
var duration = Math.floor(params.duration);
if (this.config.endTime !== null) {
duration = Math.min(this.config.endTime, duration);
}
if (
this.videoProgressSlider.slider &&
......
......@@ -196,6 +196,42 @@ function (Sjson, AsyncProcess) {
},
/**
* @desc Gets the correct start and end times from the state configuration
*
* @returns {array} if [startTime, endTime] are defined
*/
getStartEndTimes: function () {
// due to the way config.startTime/endTime are
// processed in 03_video_player.js, we assume
// endTime can be an integer or null,
// and startTime is an integer > 0
var config = this.state.config;
var startTime = config.startTime * 1000;
var endTime = (config.endTime !== null) ? config.endTime * 1000 : null;
return [startTime, endTime];
},
/**
* @desc Gets captions within the start / end times stored within this.state.config
*
* @returns {object} {start, captions} parallel arrays of
* start times and corresponding captions
*/
getBoundedCaptions: function () {
// get start and caption. If startTime and endTime
// are specified, filter by that range.
var times = this.getStartEndTimes();
var results = this.sjson.filter.apply(this.sjson, times);
var start = results.start;
var captions = results.captions;
return {
'start': start,
'captions': captions
};
},
/**
* @desc Fetch the caption file specified by the user. Upon successful
* receipt of the file, the captions will be rendered.
*
......@@ -244,9 +280,9 @@ function (Sjson, AsyncProcess) {
data: data,
success: function (sjson) {
self.sjson = new Sjson(sjson);
var start = self.sjson.getStartTimes(),
captions = self.sjson.getCaptions();
var results = self.getBoundedCaptions();
var start = results.start;
var captions = results.captions;
if (self.loaded) {
if (self.rendered) {
......@@ -622,11 +658,12 @@ function (Sjson, AsyncProcess) {
*
*/
play: function () {
var startAndCaptions, start, end;
if (this.loaded) {
if (!this.rendered) {
var start = this.sjson.getStartTimes(),
captions = this.sjson.getCaptions();
startAndCaptions = this.getBoundedCaptions();
start = startAndCaptions.start;
captions = startAndCaptions.captions;
this.renderCaption(start, captions);
}
......@@ -652,6 +689,9 @@ function (Sjson, AsyncProcess) {
*/
updatePlayTime: function (time) {
var state = this.state,
startTime,
endTime,
params,
newIndex;
if (this.loaded) {
......@@ -660,7 +700,11 @@ function (Sjson, AsyncProcess) {
}
time = Math.round(time * 1000 + 100);
newIndex = this.sjson.search(time);
var times = this.getStartEndTimes();
// if start and end times are defined, limit search.
// else, use the entire list of video captions
params = [time].concat(times);
newIndex = this.sjson.search.apply(this.sjson, params);
if (
typeof newIndex !== 'undefined' &&
......
......@@ -320,5 +320,3 @@ class CMSVideoTest(CMSVideoBaseTest):
self.save_unit_settings()
self.video.click_player_button('play')
self.assertTrue(self.video.is_slider_range_visible)
......@@ -160,28 +160,3 @@ class VideoTimesTest(VideoBaseTest):
self.video.wait_for_state('pause')
self.assertIn(self.video.position, ('0:35', '0:36'))
@flaky # TODO fix this, see TNL-1619
def test_video_finish_time_with_seek(self):
"""
Scenario: Finish Time works for Youtube video.
Given it has a video in "Youtube" mode with end-time at 1:00, the video starts playing from 2:15
And I seek video to "2:15" position
And I click video button "play"
And I wait until video stop playing
Then I see video slider at "2:20" position
"""
data = {'end_time': '00:01:00'}
self.metadata = self.metadata_for_mode('youtube', additional_data=data)
# go to video
self.navigate_to_video()
self.video.seek('2:15')
self.video.click_player_button('play')
# wait until video stop playing
self.video.wait_for_state('finished')
self.assertEqual(self.video.position, '2:20')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment