Commit 450dfe17 by Valera Rozuvan Committed by Valera Rozuvan

TESTS: Add tests for fix of end-time in the video player.

BLD-662
parent dc373f22
......@@ -117,7 +117,7 @@
runs(function () {
expect(state.videoPlayer.player.getPlayerState())
.toBe(STATUS.PLAYING);
.toBe(STATUS.BUFFERING);
});
});
......
......@@ -366,19 +366,22 @@ function (VideoPlayer) {
});
it('Slider event causes log update', function () {
runs(function () {
var currentTime = state.videoPlayer.currentTime;
spyOn(state.videoPlayer, 'log');
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 2 }
);
});
waitsFor(function () {
return state.videoPlayer.currentTime >= 2;
}, 'currentTime is less than 2 seconds', WAIT_TIMEOUT);
runs(function () {
expect(state.videoPlayer.log).toHaveBeenCalledWith(
'seek_video',
{
old_time: currentTime,
old_time: jasmine.any(Number),
new_time: 2,
type: 'onSlideSeek'
}
......@@ -388,25 +391,37 @@ function (VideoPlayer) {
it('seek the player', function () {
runs(function () {
spyOn(state.videoPlayer.player, 'seekTo');
spyOn(state.videoPlayer.player, 'seekTo').andCallThrough();
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 60 }
jQuery.Event('slide'), { value: 30 }
);
});
waitsFor(function () {
return state.videoPlayer.currentTime >= 30;
}, 'currentTime is less than 30 seconds', WAIT_TIMEOUT);
runs(function () {
expect(state.videoPlayer.player.seekTo)
.toHaveBeenCalledWith(60, true);
.toHaveBeenCalledWith(30, true);
});
});
it('call updatePlayTime on player', function () {
runs(function () {
spyOn(state.videoPlayer, 'updatePlayTime');
spyOn(state.videoPlayer, 'updatePlayTime').andCallThrough();
state.videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 60 }
jQuery.Event('slide'), { value: 30 }
);
});
waitsFor(function () {
return state.videoPlayer.currentTime >= 30;
}, 'currentTime is less than 30 seconds', WAIT_TIMEOUT);
runs(function () {
expect(state.videoPlayer.updatePlayTime)
.toHaveBeenCalledWith(60);
.toHaveBeenCalledWith(jasmine.any(Number));
});
});
......@@ -1070,6 +1085,7 @@ function (VideoPlayer) {
youtubeId: jasmine.createSpy().andReturn('videoId'),
isFlashMode: jasmine.createSpy().andReturn(false),
isHtml5Mode: jasmine.createSpy().andReturn(true),
isYoutubeType: jasmine.createSpy().andReturn(true),
setPlayerMode: jasmine.createSpy(),
videoPlayer: {
currentTime: 60,
......@@ -1104,12 +1120,12 @@ function (VideoPlayer) {
});
it('in HTML5 mode', function () {
state.isYoutubeType.andReturn(false);
VideoPlayer.prototype.setPlaybackRate.call(state, '0.75');
expect(state.videoPlayer.player.setPlaybackRate).toHaveBeenCalledWith('0.75');
});
it('Youtube video in FF, with new speed equal 1.0', function () {
state.videoType = 'youtube';
state.browserIsFirefox = true;
state.videoPlayer.isPlaying.andReturn(false);
......
......@@ -68,6 +68,7 @@ function (VideoPlayer, VideoStorage) {
initialize: initialize,
isHtml5Mode: isHtml5Mode,
isFlashMode: isFlashMode,
isYoutubeType: isYoutubeType,
parseSpeed: parseSpeed,
parseVideoSources: parseVideoSources,
parseYoutubeStreams: parseYoutubeStreams,
......@@ -847,6 +848,10 @@ function (VideoPlayer, VideoStorage) {
return this.getPlayerMode() === 'html5';
}
function isYoutubeType() {
return this.videoType === 'youtube';
}
function speedToString(speed) {
return parseFloat(speed).toFixed(2).replace(/\.00$/, '.0');
}
......
......@@ -275,12 +275,13 @@ function () {
// Register the 'play' event.
this.video.addEventListener('play', function () {
_this.playerState = HTML5Video.PlayerState.PLAYING;
_this.playerState = HTML5Video.PlayerState.BUFFERING;
_this.callStateChangeCallback();
}, false);
this.video.addEventListener('playing', function () {
_this.playerState = HTML5Video.PlayerState.PLAYING;
_this.callStateChangeCallback();
}, false);
// Register the 'pause' event.
......
......@@ -17,13 +17,24 @@ function (HTML5Video, Resizer) {
methodsDict = {
duration: duration,
handlePlaybackQualityChange: handlePlaybackQualityChange,
// Added for finer graded seeking control.
// Please see:
// https://developers.google.com/youtube/js_api_reference#Events
isBuffering: isBuffering,
// https://developers.google.com/youtube/js_api_reference#cueVideoById
isCued: isCued,
isEnded: isEnded,
isPlaying: isPlaying,
isUnstarted: isUnstarted,
log: log,
onCaptionSeek: onSeek,
onEnded: onEnded,
onPause: onPause,
onPlay: onPlay,
runTimer: runTimer,
stopTimer: stopTimer,
onPlaybackQualityChange: onPlaybackQualityChange,
onReady: onReady,
onSlideSeek: onSeek,
......@@ -54,7 +65,17 @@ function (HTML5Video, Resizer) {
// Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) {
var debouncedF = _.debounce(
function (params) {
return onSeek.call(this, params);
}.bind(state),
300
);
state.bindTo(methodsDict, state.videoPlayer, state);
state.videoPlayer.onSlideSeek = debouncedF;
state.videoPlayer.onCaptionSeek = debouncedF;
}
// function _initialize(state)
......@@ -79,10 +100,10 @@ function (HTML5Video, Resizer) {
state.videoPlayer.player.setVolume(state.currentVolume);
});
if (state.videoType === 'youtube') {
if (state.isYoutubeType()) {
state.videoPlayer.PlayerState = YT.PlayerState;
state.videoPlayer.PlayerState.UNSTARTED = -1;
} else { // if (state.videoType === 'html5') {
} else {
state.videoPlayer.PlayerState = HTML5Video.PlayerState;
}
......@@ -139,7 +160,7 @@ function (HTML5Video, Resizer) {
_updateVcrAndRegion(state);
}, false);
} else { // if (state.videoType === 'youtube') {
} else {
youTubeId = state.youtubeId();
state.videoPlayer.player = new YT.Player(state.id, {
......@@ -305,8 +326,8 @@ function (HTML5Video, Resizer) {
// 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.
function update() {
this.videoPlayer.currentTime = this.videoPlayer.player.getCurrentTime();
function update(time) {
this.videoPlayer.currentTime = time || this.videoPlayer.player.getCurrentTime();
if (isFinite(this.videoPlayer.currentTime)) {
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
......@@ -339,7 +360,7 @@ function (HTML5Video, Resizer) {
!(
this.browserIsFirefox &&
newSpeed === '1.0' &&
this.videoType === 'youtube'
this.isYoutubeType()
)
) {
this.videoPlayer.player.setPlaybackRate(newSpeed);
......@@ -404,6 +425,7 @@ function (HTML5Video, Resizer) {
return;
}
this.el.off('play.seek');
this.videoPlayer.log(
'seek_video',
{
......@@ -420,24 +442,49 @@ function (HTML5Video, Resizer) {
this.videoPlayer.stopAtEndTime = false;
}
this.videoPlayer.player.seekTo(newTime, true);
if (this.videoPlayer.isPlaying()) {
clearInterval(this.videoPlayer.updateInterval);
this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200
);
setTimeout(this.videoPlayer.update, 0);
this.videoPlayer.stopTimer();
} else {
this.videoPlayer.currentTime = newTime;
}
var isUnplayed = this.videoPlayer.isUnstarted() ||
this.videoPlayer.isCued();
this.videoPlayer.updatePlayTime(newTime);
// Use `cueVideoById` method for youtube video that is not played before.
if (isUnplayed && this.isYoutubeType()) {
this.videoPlayer.player.cueVideoById(this.youtubeId(), newTime);
} else {
// Youtube video cannot be rewinded during bufferization, so wait to
// finish bufferization and then rewind the video.
if (this.isYoutubeType() && this.videoPlayer.isBuffering()) {
this.el.on('play.seek', function () {
this.videoPlayer.player.seekTo(newTime, true);
}.bind(this));
} else {
// Otherwise, just seek the video
this.videoPlayer.player.seekTo(newTime, true);
}
}
this.videoPlayer.updatePlayTime(newTime, true);
this.el.trigger('seek', arguments);
}
function runTimer() {
if (!this.videoPlayer.updateInterval) {
this.videoPlayer.updateInterval = window.setInterval(
this.videoPlayer.update, 200
);
this.videoPlayer.update();
}
}
function stopTimer() {
window.clearInterval(this.videoPlayer.updateInterval);
delete this.videoPlayer.updateInterval;
}
function onEnded() {
var time = this.videoPlayer.duration();
......@@ -466,8 +513,7 @@ function (HTML5Video, Resizer) {
}
);
clearInterval(this.videoPlayer.updateInterval);
delete this.videoPlayer.updateInterval;
this.videoPlayer.stopTimer();
this.trigger('videoControl.pause', null);
this.saveState(true);
......@@ -482,14 +528,7 @@ function (HTML5Video, Resizer) {
}
);
if (!this.videoPlayer.updateInterval) {
this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200
);
this.videoPlayer.update();
}
this.videoPlayer.runTimer();
this.trigger('videoControl.play', null);
this.trigger('videoProgressSlider.notifyThroughHandleEnd', {
end: false
......@@ -558,7 +597,7 @@ function (HTML5Video, Resizer) {
// https://github.com/edx/edx-platform/pull/2841
if (
(this.isHtml5Mode() || availablePlaybackRates.length > 1) &&
this.videoType === 'youtube'
this.isYoutubeType()
) {
if (availablePlaybackRates.length === 1 && !this.isTouch) {
// This condition is needed in cases when Firefox version is
......@@ -620,20 +659,34 @@ function (HTML5Video, Resizer) {
}
function onStateChange(event) {
this.el.removeClass([
'is-unstarted', 'is-playing', 'is-paused', 'is-buffered',
'is-ended', 'is-cued'
].join(' '));
switch (event.data) {
case this.videoPlayer.PlayerState.UNSTARTED:
this.el.addClass('is-unstarted');
this.videoPlayer.onUnstarted();
break;
case this.videoPlayer.PlayerState.PLAYING:
this.el.addClass('is-playing');
this.videoPlayer.onPlay();
break;
case this.videoPlayer.PlayerState.PAUSED:
this.el.addClass('is-paused');
this.videoPlayer.onPause();
break;
case this.videoPlayer.PlayerState.BUFFERING:
this.el.addClass('is-buffered');
this.el.trigger('buffering');
break;
case this.videoPlayer.PlayerState.ENDED:
this.el.addClass('is-ended');
this.videoPlayer.onEnded();
break;
case this.videoPlayer.PlayerState.CUED:
this.el.addClass('is-cued');
this.videoPlayer.player.seekTo(this.videoPlayer.seekToTimeOnCued, true);
// We need to call play() explicitly because after the call
// to functions cueVideoById() followed by seekTo() the video
......@@ -711,12 +764,12 @@ function (HTML5Video, Resizer) {
return time;
}
function updatePlayTime(time) {
function updatePlayTime(time, skip_seek) {
var videoPlayer = this.videoPlayer,
duration = this.videoPlayer.duration(),
youTubeId;
if (duration > 0 && videoPlayer.goToStartTime) {
if (duration > 0 && videoPlayer.goToStartTime && !skip_seek) {
videoPlayer.goToStartTime = false;
// The duration might have changed. Update the start-end time region to
......@@ -746,7 +799,7 @@ function (HTML5Video, Resizer) {
//
// HTML5 video sources play fine from start-time in both Chrome
// and Firefox.
if (this.browserIsFirefox && this.videoType === 'youtube') {
if (this.browserIsFirefox && this.isYoutubeType()) {
youTubeId = this.youtubeId();
// When we will call cueVideoById() for some strange reason
......@@ -794,10 +847,27 @@ function (HTML5Video, Resizer) {
}
function isPlaying() {
var playerState = this.videoPlayer.player.getPlayerState(),
PLAYING = this.videoPlayer.PlayerState.PLAYING;
var playerState = this.videoPlayer.player.getPlayerState();
return playerState === this.videoPlayer.PlayerState.PLAYING;
}
return playerState === PLAYING;
function isBuffering() {
var playerState = this.videoPlayer.player.getPlayerState();
return playerState === this.videoPlayer.PlayerState.BUFFERING;
}
function isCued() {
var playerState = this.videoPlayer.player.getPlayerState();
return playerState === this.videoPlayer.PlayerState.CUED;
}
function isUnstarted() {
var playerState = this.videoPlayer.player.getPlayerState();
return playerState === this.videoPlayer.PlayerState.UNSTARTED;
}
/*
......@@ -844,7 +914,7 @@ function (HTML5Video, Resizer) {
// might differ by one or two seconds against the actual time as will
// be reported later on by the player.getDuration() API function.
if (!isFinite(dur) || dur <= 0) {
if (this.videoType === 'youtube') {
if (this.isYoutubeType()) {
dur = this.getDuration();
}
}
......@@ -873,9 +943,9 @@ function (HTML5Video, Resizer) {
});
}
if (this.videoType === 'youtube') {
if (this.isYoutubeType()) {
logInfo.code = this.youtubeId();
} else if (this.videoType === 'html5') {
} else {
logInfo.code = 'html5';
}
......
......@@ -177,15 +177,26 @@ function () {
}
function onSlide(event, ui) {
var time = ui.value,
duration = this.videoPlayer.duration();
this.videoProgressSlider.frozen = true;
// Remember the seek to value so that we don't repeat ourselves on the
// 'stop' slider event.
this.videoProgressSlider.lastSeekValue = ui.value;
this.videoProgressSlider.lastSeekValue = time;
this.trigger(
'videoControl.updateVcrVidTime',
{
time: time,
duration: duration
}
);
this.trigger(
'videoPlayer.onSlideSeek',
{'type': 'onSlideSeek', 'time': ui.value}
{'type': 'onSlideSeek', 'time': time}
);
// ARIA
......
......@@ -35,7 +35,7 @@ def configure_screenshots_for_all_steps(_step, action):
else:
raise ValueError('Parameter `action` should be one of "enable" or "disable".')
@world.absorb
def capture_screenshot_before_after(func):
"""
A decorator that will take a screenshot before and after the applied
......
......@@ -2,18 +2,6 @@
Feature: LMS.Video component
As a student, I want to view course videos in LMS
# 1 Disabled 4/8/14 after intermittent failures in master
#Scenario: Video component stores position correctly when page is reloaded
# Given the course has a Video component in "Youtube" mode
# When the video has rendered in "Youtube" mode
# And I click video button "play"
# And I click video button "pause"
# Then I seek video to "10" seconds
# And I click video button "play"
# And I click video button "pause"
# And I reload the page with video
# Then I see video slider at "10" seconds
# 1
Scenario: Video component is fully rendered in the LMS in HTML5 mode
Given the course has a Video component in "HTML5" mode
......@@ -267,7 +255,106 @@ Feature: LMS.Video component
Then the video has rendered in "HTML5" mode
And the video does not show the captions
# 20 Disabled 4/8/14 after intermittent failures in master
# 20
Scenario: Start time works for Youtube video
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| start_time |
| 00:00:10 |
And I click video button "play"
Then I see video slider at "0:10" position
# 21
Scenario: End time works for Youtube video
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| end_time |
| 00:00:02 |
And I click video button "play"
And I wait "5" seconds
Then I see video slider at "0:02" position
# 22
Scenario: Youtube video with end-time at 1:00 and the video starts playing at 0:58
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| end_time |
| 00:01:00 |
And I wait for video controls appear
And I seek video to "0:58" position
And I click video button "play"
And I wait "5" seconds
Then I see video slider at "1:00" position
# 23
Scenario: Start time and end time work together for Youtube video
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| start_time | end_time |
| 00:00:10 | 00:00:12 |
And I click video button "play"
Then I see video slider at "0:10" position
And I wait "5" seconds
Then I see video slider at "0:12" position
# 24
Scenario: Youtube video after pausing at end time video plays to the end from end time
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| start_time | end_time |
| 00:01:51 | 00:01:52 |
And I click video button "play"
And I wait "5" seconds
# The end time is 00:01:52.
Then I see video slider at "1:52" position
And I click video button "play"
And I wait "8" seconds
# The default video length is 00:01:55.
Then I see video slider at "1:55" position
# 25
Scenario: Youtube video with end-time at 0:32 and start-time at 0:30, the video starts playing from 0:28
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| start_time | end_time |
| 00:00:30 | 00:00:32 |
And I wait for video controls appear
And I seek video to "0:28" position
And I click video button "play"
And I wait "8" seconds
Then I see video slider at "0:32" position
# 26
Scenario: Youtube video with end-time at 1:00, the video starts playing from 1:52
Given I am registered for the course "test_course"
And it has a video in "Youtube" mode:
| end_time |
| 00:01:00 |
And I wait for video controls appear
And I seek video to "1:52" position
And I click video button "play"
And I wait "5" seconds
# Video stops at the end.
Then I see video slider at "1:55" position
# 27
@skip_firefox
Scenario: Quality button appears on play
Given the course has a Video component in "Youtube" mode
Then I see video button "quality" is hidden
And I click video button "play"
Then I see video button "quality" is visible
# 28
@skip_firefox
Scenario: Quality button works correctly
Given the course has a Video component in "Youtube" mode
And I click video button "play"
And I see video button "quality" is inactive
And I click video button "quality"
Then I see video button "quality" is active
# 29 Disabled 4/8/14 after intermittent failures in master
#Scenario: Transcripts are available on different speeds of Flash mode
# Given I am registered for the course "test_course"
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
......@@ -282,7 +369,7 @@ Feature: LMS.Video component
# Then I select the "1.25" speed
# And I see "Hi, welcome to Edx." text in the captions
# 21 Disabled 4/8/14 after intermittent failures in master
# 30 Disabled 4/8/14 after intermittent failures in master
#Scenario: Elapsed time calculates correctly on different speeds of Flash mode
# Given I am registered for the course "test_course"
# And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
......@@ -298,19 +385,14 @@ Feature: LMS.Video component
# And I click video button "pause"
# And I click on caption line "2", video module shows elapsed time "4"
# 27
@skip_firefox
Scenario: Quality button appears on play
Given the course has a Video component in "Youtube" mode
Then I see video button "quality" is hidden
And I click video button "play"
Then I see video button "quality" is visible
# 28
@skip_firefox
Scenario: Quality button works correctly
Given the course has a Video component in "Youtube" mode
And I click video button "play"
And I see video button "quality" is inactive
And I click video button "quality"
Then I see video button "quality" is active
# 31 Disabled 4/8/14 after intermittent failures in master
#Scenario: Video component stores position correctly when page is reloaded
# Given the course has a Video component in "Youtube" mode
# When the video has rendered in "Youtube" mode
# And I click video button "play"
# And I click video button "pause"
# Then I seek video to "0:10" position
# And I click video button "play"
# And I click video button "pause"
# And I reload the page with video
# Then I see video slider at "0:10" position
......@@ -482,6 +482,7 @@ def check_captions(_step):
@step('I select language with code "([^"]*)"$')
def select_language(_step, code):
world.wait_for_visible('.video-controls')
# Make sure that all ajax requests that affects the language menu are finished.
# For example, request to get new translation etc.
world.wait_for_ajax_complete()
......@@ -506,16 +507,19 @@ def select_language(_step, code):
@step('I click video button "([^"]*)"$')
def click_button(_step, button):
world.css_click(VIDEO_BUTTONS[button])
world.wait_for_ajax_complete()
if button == "play":
# Needs to wait for video buffrization
world.wait_for(
func=lambda _: world.css_has_class('.video', 'is-playing') and world.is_css_present(VIDEO_BUTTONS['pause']),
timeout=30
)
world.wait_for_ajax_complete()
@step('I see video slider at "([^"]*)" seconds$')
def start_playing_video_from_n_seconds(_step, position):
world.wait_for(
func=lambda _: elapsed_time() > 0,
timeout=30
)
@step('I see video slider at "([^"]*)" position$')
def start_playing_video_from_n_seconds(_step, time_str):
position = parse_time_str(time_str)
actual_position = elapsed_time()
assert_equal(actual_position, int(position), "Current position is {}, but should be {}".format(actual_position, position))
......@@ -530,12 +534,21 @@ def i_see_duration(_step, position):
assert duration() == parse_time_str(position)
@step('I seek video to "([^"]*)" seconds$')
def seek_video_to_n_seconds(_step, seconds):
time = float(seconds.strip())
jsCode = "$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0:f}}})".format(time)
@step('I wait for video controls appear$')
def controls_appear(_step):
world.wait_for_visible('.video-controls')
@step('I seek video to "([^"]*)" position$')
def seek_video_to_n_seconds(_step, time_str):
time = parse_time_str(time_str)
jsCode = "$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0}}})".format(time)
world.browser.execute_script(jsCode)
_step.given('I see video slider at "{}" seconds'.format(seconds))
world.wait_for(
func=lambda _: world.retry_on_exception(lambda: elapsed_time() == time and not world.css_has_class('.video', 'is-buffering')),
timeout=30
)
_step.given('I see video slider at "{0}" position'.format(time_str))
@step('I have a "([^"]*)" transcript file in assets$')
......
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