Skip to content

Commit

Permalink
Fix src= tests
Browse files Browse the repository at this point in the history
The integration tests for src= failed almost universally on Tizen, but
a few failed on other platforms, as well.  The issue was the fixed
delays in those tests.  This replaces those fixed delays with an
event-based utility that solved the same problem in other Player
integration tests.

Issue #816
Issue #997

Change-Id: Ib43cbb139ef77be1219e60d1fd5009aa403cc4cb
  • Loading branch information
joeyparrish committed Apr 16, 2019
1 parent c2667c1 commit 322876d
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 94 deletions.
83 changes: 6 additions & 77 deletions test/player_external.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
describe('Player', () => {
const Util = shaka.test.Util;
const Feature = shakaAssets.Feature;
const waitForMovementOrFailOnTimeout = Util.waitForMovementOrFailOnTimeout;
const waitForEndOrTimeout = Util.waitForEndOrTimeout;

/** @type {!jasmine.Spy} */
let onErrorSpy;
Expand Down Expand Up @@ -170,10 +172,10 @@ describe('Player', () => {

// Wait for the video to start playback. If it takes longer than 20
// seconds, fail the test.
await waitForMovementOrFailOnTimeout(video, 20);
await waitForMovementOrFailOnTimeout(eventManager, video, 20);

// Play for 30 seconds, but stop early if the video ends.
await waitForEndOrTimeout(video, 30);
await waitForEndOrTimeout(eventManager, video, 30);

if (video.ended) {
checkEndedTime();
Expand All @@ -193,10 +195,10 @@ describe('Player', () => {

// Wait for the video to start playback again after seeking. If it
// takes longer than 20 seconds, fail the test.
await waitForMovementOrFailOnTimeout(video, 20);
await waitForMovementOrFailOnTimeout(eventManager, video, 20);

// Play for 30 seconds, but stop early if the video ends.
await waitForEndOrTimeout(video, 30);
await waitForEndOrTimeout(eventManager, video, 30);

// By now, ended should be true.
expect(video.ended).toBe(true);
Expand Down Expand Up @@ -229,79 +231,6 @@ describe('Player', () => {
}
});

/**
* Wait for the video playhead to move forward by some meaningful delta.
* If this happens before |timeout| seconds pass, the Promise is resolved.
* Otherwise, the Promise is rejected.
*
* @param {!HTMLMediaElement} target
* @param {number} timeout in seconds, after which the Promise fails
* @return {!Promise}
*/
function waitForMovementOrFailOnTimeout(target, timeout) {
const curEventManager = eventManager;
const timeGoal = target.currentTime + 1;
let goalMet = false;
const startTime = Date.now();
shaka.log.info('Waiting for movement from', target.currentTime,
'to', timeGoal);

return new Promise((resolve, reject) => {
curEventManager.listen(target, 'timeupdate', () => {
if (target.currentTime >= timeGoal) {
goalMet = true;
const endTime = Date.now();
const seconds = ((endTime - startTime) / 1000).toFixed(2);
shaka.log.info('Movement goal met after ' + seconds + ' seconds');

curEventManager.unlisten(target, 'timeupdate');
resolve();
}
});

Util.delay(timeout).then(() => {
// This check is only necessary to supress the error log. It's fine to
// unlisten twice or to reject after resolve. Neither of those actions
// matter. But the error log can be confusing during debugging if we
// have already met the movement goal.
if (!goalMet) {
shaka.log.error('Timeout waiting for playback.',
'current time', target.currentTime,
'ready state', target.readyState,
'playback rate', target.playbackRate,
'paused', target.paused,
'buffered', JSON.stringify(player.getBufferedInfo()));

curEventManager.unlisten(target, 'timeupdate');
reject(new Error('Timeout while waiting for playback!'));
}
});
});
}

/**
* Wait for the video to end or for |timeout| seconds to pass, whichever
* occurs first. The Promise is resolved when either of these happens.
*
* @param {!HTMLMediaElement} target
* @param {number} timeout in seconds, after which the Promise succeeds
* @return {!Promise}
*/
function waitForEndOrTimeout(target, timeout) {
const curEventManager = eventManager;

return new Promise((resolve, reject) => {
const callback = () => {
curEventManager.unlisten(target, 'ended');
resolve();
};

// Whichever happens first resolves the Promise.
curEventManager.listen(target, 'ended', callback);
Util.delay(timeout).then(callback);
});
}

/**
* Check the video time for videos that we expect to have ended.
*/
Expand Down
42 changes: 25 additions & 17 deletions test/player_src_equals_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@
// |HTMLMediaElement.src=|. These tests are to verify that all |shaka.Player|
// public methods behaviour correctly when playing content video |src=|.
describe('Player Src Equals', () => {
const Util = shaka.test.Util;
const waitForMovementOrFailOnTimeout = Util.waitForMovementOrFailOnTimeout;

const SMALL_MP4_CONTENT_URI = '/base/test/test/assets/small.mp4';

/** @type {!HTMLVideoElement} */
let video;
/** @type {!shaka.Player} */
let player;
/** @type {shaka.util.EventManager} */
let eventManager;

beforeAll(() => {
video = shaka.util.Dom.createVideoElement();
Expand All @@ -34,11 +39,14 @@ describe('Player Src Equals', () => {
beforeEach(() => {
player = new shaka.Player();
player.addEventListener('error', fail);
eventManager = new shaka.util.EventManager;
});

afterEach(async () => {
await player.destroy();

eventManager.release();

// Work-around: allow the Tizen media pipeline to cool down.
// Without this, Tizen's pipeline seems to hang in subsequent tests.
// TODO: file a bug on Tizen
Expand Down Expand Up @@ -93,19 +101,19 @@ describe('Player Src Equals', () => {
expect(seekRange.end).toBeCloseTo(video.duration);
expect(video.duration).not.toBeCloseTo(0);

// Start playback and let it play for a little. We give it a couple second
// so that any start-up latency from the media stack will be forgiven.
// Start playback and wait for the playhead to move.
video.play();
await shaka.test.Util.delay(2);
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);

// Make sure the playhead is roughly where we expect it to be before
// seeking.
expect(video.currentTime).toBeGreaterThan(0);
expect(video.currentTime).toBeLessThan(2.0);

// Trigger a seek and then wait a little to allow the seek to take effect.
// Trigger a seek and then wait for the seek to take effect.
// This seek target is very close to the duration of the video.
video.currentTime = 10;
await shaka.test.Util.delay(0.5);
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);

// Make sure the playhead is roughly where we expect it to be after
// seeking.
Expand All @@ -129,9 +137,9 @@ describe('Player Src Equals', () => {
it('reports buffering information', async () => {
await loadWithSrcEquals(SMALL_MP4_CONTENT_URI);

// Wait one second in hopes that it will allow us to buffer more than one
// second of content.
await shaka.test.Util.delay(1);
// For playback to begin so that we have some content buffered.
video.play();
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);

const buffered = player.getBufferedInfo();

Expand All @@ -140,31 +148,29 @@ describe('Player Src Equals', () => {
expect(buffered.video).toEqual([]);
expect(buffered.text).toEqual([]);

// We should have an overall view of buffering. We gave ourselves a second
// to buffer content, so we should have more than one second worth of
// content (hopefully).
// We should have an overall view of buffering. We waited for playback,
// so we should have some content buffered.
expect(buffered.total).toBeTruthy();
expect(buffered.total.length).toBe(1);
expect(buffered.total[0].start).toBeCloseTo(0);
expect(buffered.total[0].end).toBeGreaterThan(1);
expect(buffered.total[0].end).toBeGreaterThan(0);
});

// When we load content via src=, can we use the trick play controls to
// control the playback rate?
it('can control trick play rate', async () => {
await loadWithSrcEquals(SMALL_MP4_CONTENT_URI);

video.play();

// Let playback run for a little.
await shaka.test.Util.delay(0.1);
video.play();
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);

// Enabling trick play should change our playback rate to the same rate.
player.trickPlay(2);
expect(video.playbackRate).toBe(2);

// Let playback continue playing for a bit longer.
await shaka.test.Util.delay(0.5);
await shaka.test.Util.delay(2);

// Cancelling trick play should return our playback rate to normal.
player.cancelTrickPlay();
Expand Down Expand Up @@ -327,6 +333,7 @@ describe('Player Src Equals', () => {

// Start playback and wait. We should see the playhead move.
video.play();
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);
await shaka.test.Util.delay(1.5);

// When checking if the playhead moved, check for less progress than time we
Expand All @@ -342,7 +349,8 @@ describe('Player Src Equals', () => {

// Wait some time for playback to start so that we will have a load latency
// value.
await shaka.test.Util.delay(0.2);
video.play();
await waitForMovementOrFailOnTimeout(eventManager, video, /* timeout= */10);

// Get the stats and check that some stats have been filled in.
const stats = player.getStats();
Expand Down
82 changes: 82 additions & 0 deletions test/test/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,88 @@ shaka.test.Util.setupCSS = async function(cssLink) {
/* modifyVars*/ false, /* clearFileCache */ false);
};

/**
* Wait for the video playhead to move forward by some meaningful delta.
* If this happens before |timeout| seconds pass, the Promise is resolved.
* Otherwise, the Promise is rejected.
*
* @param {shaka.util.EventManager} eventManager
* @param {!HTMLMediaElement} target
* @param {number} timeout in seconds, after which the Promise fails
* @return {!Promise}
*/
shaka.test.Util.waitForMovementOrFailOnTimeout =
(eventManager, target, timeout) => {
const timeGoal = target.currentTime + 1;
let goalMet = false;
const startTime = Date.now();
console.assert(!target.ended, 'Video should not be ended!');
shaka.log.info('Waiting for movement from', target.currentTime,
'to', timeGoal);

return new Promise((resolve, reject) => {
eventManager.listen(target, 'timeupdate', () => {
if (target.currentTime >= timeGoal || target.ended) {
goalMet = true;
const endTime = Date.now();
const seconds = ((endTime - startTime) / 1000).toFixed(2);
shaka.log.info('Movement goal met after ' + seconds + ' seconds');

eventManager.unlisten(target, 'timeupdate');
resolve();
}
});

shaka.test.Util.delay(timeout).then(() => {
// This check is only necessary to supress the error log. It's fine to
// unlisten twice or to reject after resolve. Neither of those actions
// matter. But the error log can be confusing during debugging if we
// have already met the movement goal.
if (!goalMet) {
const buffered = [];
for (let i = 0; i < target.buffered.length; ++i) {
buffered.push({
start: target.buffered.start(i),
end: target.buffered.end(i),
});
}

shaka.log.error('Timeout waiting for playback.',
'current time', target.currentTime,
'ready state', target.readyState,
'playback rate', target.playbackRate,
'paused', target.paused,
'buffered', buffered);

eventManager.unlisten(target, 'timeupdate');
reject(new Error('Timeout while waiting for playback!'));
}
});
});
};

/**
* Wait for the video to end or for |timeout| seconds to pass, whichever
* occurs first. The Promise is resolved when either of these happens.
*
* @param {shaka.util.EventManager} eventManager
* @param {!HTMLMediaElement} target
* @param {number} timeout in seconds, after which the Promise succeeds
* @return {!Promise}
*/
shaka.test.Util.waitForEndOrTimeout = (eventManager, target, timeout) => {
return new Promise((resolve, reject) => {
const callback = () => {
eventManager.unlisten(target, 'ended');
resolve();
};

// Whichever happens first resolves the Promise.
eventManager.listen(target, 'ended', callback);
shaka.test.Util.delay(timeout).then(callback);
});
};

/**
* @const
* @private
Expand Down

0 comments on commit 322876d

Please sign in to comment.