mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-22 17:17:09 +03:00
93bcf28e41
Change-Id: I9b50b581b341302fbeaf127c411e16d9927facf2
384 lines
12 KiB
JavaScript
384 lines
12 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2015 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
goog.require('shaka.dash.MpdRequest');
|
|
goog.require('shaka.media.Stream');
|
|
goog.require('shaka.player.Player');
|
|
goog.require('shaka.polyfill.installAll');
|
|
goog.require('shaka.util.EventManager');
|
|
|
|
describe('Live', function() {
|
|
var originalTimeout;
|
|
var originalSend;
|
|
var video;
|
|
var videoSource;
|
|
var player;
|
|
var eventManager;
|
|
|
|
const segmentDurationManifestUrl =
|
|
'//vm2.dashif.org/livesim/testpic_6s/Manifest.mpd';
|
|
const googleStorageUrl = '//storage.googleapis.com/';
|
|
const segmentTimeDir =
|
|
googleStorageUrl + 'widevine-demo-media/oops-segment-timeline-time/';
|
|
const segmentTimeMpd =
|
|
segmentTimeDir + 'oops-segment-timeline-time-oops_video.mpd';
|
|
const segmentNumberDir =
|
|
googleStorageUrl + 'widevine-demo-media/oops-segment-timeline-number/';
|
|
const segmentNumberMpd =
|
|
segmentNumberDir + 'oops-segment-timeline-number-oops_video.mpd';
|
|
const segmentNumberMinusFiveMpd =
|
|
segmentNumberDir + 'oops-segment-timeline-number-oops_video_minus_5.mpd';
|
|
const segmentNumberPlusFiveMpd =
|
|
segmentNumberDir + 'oops-segment-timeline-number-oops_video_plus_5.mpd';
|
|
|
|
const FUDGE_FACTOR = 5;
|
|
const SMALL_FUDGE_FACTOR = 2;
|
|
const SEEK_OFFSET = 10000;
|
|
|
|
beforeAll(function() {
|
|
// Hijack assertions and convert failed assertions into failed tests.
|
|
assertsToFailures.install();
|
|
|
|
// Change the timeout.
|
|
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 40000; // ms
|
|
|
|
// Install polyfills.
|
|
shaka.polyfill.installAll();
|
|
|
|
// Create a video tag. This will be visible so that long tests do not
|
|
// create the illusion of the test-runner being hung.
|
|
video = document.createElement('video');
|
|
video.crossOrigin = 'anonymous';
|
|
video.width = 600;
|
|
video.height = 400;
|
|
|
|
// Add it to the DOM.
|
|
document.body.appendChild(video);
|
|
|
|
// Hijack MpdRequest to set @availabilityStartTime for all MPDs from
|
|
// Cloud Storage to simulate dynamically generated MPDs.
|
|
// Note: currentTime - @availabilityStartTime must be at least as long as
|
|
// the video.
|
|
var availabilityStartTime = (Date.now() / 1000.0) - 300;
|
|
originalSend = shaka.dash.MpdRequest.prototype.send;
|
|
shaka.dash.MpdRequest.prototype.send = function() {
|
|
return originalSend.call(this).then(
|
|
function(mpd) {
|
|
if (this.url_.toString().indexOf(googleStorageUrl) >= 0) {
|
|
mpd.availabilityStartTime = availabilityStartTime;
|
|
}
|
|
return Promise.resolve(mpd);
|
|
}.bind(this));
|
|
};
|
|
});
|
|
|
|
// FIXME: This is a workaround for http://crbug.com/507916 and should be
|
|
// removed once unit tests are no longer running against affected versions
|
|
// of Chrome.
|
|
var playerErrorHandler = function(event) {
|
|
if (event.detail.message ==
|
|
shaka.player.Player.MEDIA_ERROR_MAP_[MediaError.MEDIA_ERR_DECODE]) {
|
|
console.warn('http://crbug.com/507916', event.detail.message);
|
|
|
|
// Effectively, reboot the test.
|
|
videoSource = newSource(segmentDurationManifestUrl);
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
});
|
|
|
|
// Don't trigger this handler a second time.
|
|
playerErrorHandler = convertErrorToTestFailure;
|
|
return;
|
|
}
|
|
convertErrorToTestFailure(event);
|
|
};
|
|
|
|
beforeEach(function() {
|
|
// Create a new player.
|
|
player = new shaka.player.Player(video);
|
|
player.addEventListener('error', playerErrorHandler, false);
|
|
|
|
// Disable automatic adaptation unless it is needed for a test.
|
|
// This makes test results more reproducible.
|
|
player.configure({'enableAdaptation': false});
|
|
|
|
eventManager = new shaka.util.EventManager();
|
|
});
|
|
|
|
afterEach(function(done) {
|
|
eventManager.destroy();
|
|
eventManager = null;
|
|
|
|
player.destroy().then(function() {
|
|
player = null;
|
|
done();
|
|
});
|
|
});
|
|
|
|
afterAll(function() {
|
|
// Remove the video tag from the DOM.
|
|
document.body.removeChild(video);
|
|
|
|
// Restore send().
|
|
shaka.dash.MpdRequest.prototype.send = originalSend;
|
|
|
|
// Restore the timeout.
|
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
|
|
|
|
// Restore normal assertion behavior.
|
|
assertsToFailures.uninstall();
|
|
});
|
|
|
|
describe('live support for segment duration template', function() {
|
|
beforeEach(function() {
|
|
videoSource = newSource(segmentDurationManifestUrl);
|
|
});
|
|
|
|
it('plays two full segments of content', function(done) {
|
|
const SEGMENT_LENGTH = 6;
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
var targetTime = video.currentTime + (2 * SEGMENT_LENGTH);
|
|
var waitTime = (2 * SEGMENT_LENGTH) + FUDGE_FACTOR;
|
|
return waitForTargetTime(video, eventManager, targetTime, waitTime);
|
|
}).then(function() {
|
|
done();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('returns to seek range when seeking before start', function(done) {
|
|
seekBeforeRange(videoSource, done);
|
|
});
|
|
|
|
it('returns to end of seek range when after end', function(done) {
|
|
seekAfterRange(videoSource, done);
|
|
});
|
|
});
|
|
|
|
describe('live support for segment number template', function() {
|
|
beforeEach(function() {
|
|
videoSource = newSource(segmentNumberMpd);
|
|
});
|
|
|
|
it('requests MPD update in expected time', function(done) {
|
|
requestMpdUpdate(segmentNumberMpd, videoSource, done);
|
|
});
|
|
|
|
it('returns to seek range when seeking before start', function(done) {
|
|
seekBeforeRange(videoSource, done);
|
|
});
|
|
|
|
it('returns to end of seek range when after end', function(done) {
|
|
seekAfterRange(videoSource, done);
|
|
});
|
|
|
|
it('corrects a timestamp that is behind', function(done) {
|
|
correctTimestamp(segmentNumberMinusFiveMpd, 18, done);
|
|
});
|
|
|
|
it('corrects a timestamp that is ahead', function(done) {
|
|
correctTimestamp(segmentNumberPlusFiveMpd, 19, done);
|
|
});
|
|
});
|
|
|
|
describe('live support for segment time template', function() {
|
|
beforeEach(function() {
|
|
videoSource = newSource(segmentTimeMpd);
|
|
});
|
|
|
|
it('requests MPD update in expected time', function(done) {
|
|
requestMpdUpdate(segmentTimeMpd, videoSource, done);
|
|
});
|
|
|
|
it('returns to seek range when seeking before start', function(done) {
|
|
seekBeforeRange(videoSource, done);
|
|
});
|
|
|
|
it('returns to end of seek range when after end', function(done) {
|
|
seekAfterRange(videoSource, done);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Passes test if timestamp is corrected properly and playback starts.
|
|
* @param {string} manifestUrl
|
|
* @param {number} initalReference The reference at which playback will start.
|
|
* @param {!done} done The done function, to signal the end of this test.
|
|
*/
|
|
function correctTimestamp(manifestUrl, initialReference, done) {
|
|
videoSource = newSource(manifestUrl);
|
|
var setTestExpectations = function(segmentIndexes) {
|
|
console.assert(segmentIndexes.length > 0);
|
|
var index = segmentIndexes[0];
|
|
expect(video.buffered.start(0)).toEqual(
|
|
index.references[initialReference].startTime);
|
|
waitForMovement(video, eventManager).then(function() {
|
|
done();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
};
|
|
|
|
waitForBeginPlayback(setTestExpectations);
|
|
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Passes test when MpdRequest is sent.
|
|
* @param {string} targetMpdUrl The url that should be used in the MpdRequest.
|
|
* @param {!IVideoSource} videoSource
|
|
* @param {!done} done The done function, to signal the end of this test.
|
|
*/
|
|
function requestMpdUpdate(targetMpdUrl, videoSource, done) {
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
return waitForMovement(video, eventManager);
|
|
}).then(function() {
|
|
// Seek back to ensure there is enough video to get an update.
|
|
video.currentTime -= 10;
|
|
return waitForMpdRequest(targetMpdUrl);
|
|
}).then(function() {
|
|
expect(video.currentTime).toBeGreaterThan(0.0);
|
|
done();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Seeks before the seek range start, then completes if video snaps back to
|
|
* seek range start or fails otherwise.
|
|
* @param {!IVideoSource} videoSource
|
|
* @param {!done} done The done function, to signal the end of this test.
|
|
*/
|
|
function seekBeforeRange(videoSource, done) {
|
|
// There are two 'seekrangechanged' events prior to beginning the playback.
|
|
// The second one includes the timestamp correction.
|
|
var count = 0;
|
|
|
|
var continueTest = function(event) {
|
|
if (++count < 2) return;
|
|
eventManager.unlisten(videoSource, 'seekrangechanged');
|
|
var seekStart = event.start;
|
|
delay(1.0).then(function() {
|
|
video.currentTime = seekStart - SEEK_OFFSET;
|
|
return waitForMovement(video, eventManager);
|
|
}).then(function() {
|
|
expect(videoSource.video.currentTime).toBeGreaterThan(
|
|
seekStart - SMALL_FUDGE_FACTOR);
|
|
done();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
};
|
|
|
|
eventManager.listen(videoSource, 'seekrangechanged', continueTest);
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Seeks after the seek range end, then completes if video snaps back to seek
|
|
* range end or fails otherwise.
|
|
* @param {!IVideoSource} videoSource
|
|
* @param {!done} done The done function, to signal the end of this test.
|
|
*/
|
|
function seekAfterRange(videoSource, done) {
|
|
var count = 0;
|
|
|
|
var continueTest = function(event) {
|
|
if (++count < 2) return;
|
|
eventManager.unlisten(videoSource, 'seekrangechanged');
|
|
var seekEnd = event.end;
|
|
delay(1.0).then(function() {
|
|
video.currentTime = seekEnd + SEEK_OFFSET;
|
|
return waitForMovement(video, eventManager);
|
|
}).then(function() {
|
|
expect(videoSource.video.currentTime).toBeLessThan(
|
|
seekEnd + SMALL_FUDGE_FACTOR);
|
|
done();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
};
|
|
|
|
eventManager.listen(videoSource, 'seekrangechanged', continueTest);
|
|
player.load(videoSource).then(function() {
|
|
video.play();
|
|
}).catch(function(error) {
|
|
fail(error);
|
|
done();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} targetMpdUrl The url that should be used in the MpdRequest.
|
|
* @return {!Promise} resolved when an MpdRequest has been sent.
|
|
*/
|
|
function waitForMpdRequest(targetMpdUrl) {
|
|
var requestStatus = new shaka.util.PublicPromise();
|
|
var MpdRequest = shaka.dash.MpdRequest;
|
|
|
|
spyOn(window.shaka.dash, 'MpdRequest').and.callFake(function(mpdUrl) {
|
|
expect(mpdUrl.toString()).toEqual(targetMpdUrl);
|
|
var request = new MpdRequest(mpdUrl);
|
|
spyOn(request, 'send').and.callFake(function() {
|
|
requestStatus.resolve();
|
|
var error = new Error();
|
|
error.type = 'fake';
|
|
return Promise.reject(error);
|
|
});
|
|
return request;
|
|
});
|
|
|
|
return requestStatus;
|
|
}
|
|
|
|
/**
|
|
* Waits until beginPlayback_() has been called to catch the corrected
|
|
* segmentIndexes, then bind it to the given function and set a Timeout to
|
|
* call asynchronously.
|
|
* @param {Function} fn The function to be called with the segmentIndexes.
|
|
*/
|
|
function waitForBeginPlayback(fn) {
|
|
var originalBeginPlayback = videoSource.beginPlayback_;
|
|
videoSource.beginPlayback_ = function(segmentIndexes, tsc) {
|
|
originalBeginPlayback.call(videoSource, segmentIndexes, tsc);
|
|
// Do this async.
|
|
window.setTimeout(fn.bind(null, segmentIndexes, tsc), 0);
|
|
};
|
|
}
|
|
});
|