Files
shaka-player/test/player_integration.js
T
Sandra Lokshina c70367dc97 Separate text parsing and display logic.
Closes #796.
Closes #923.

Change-Id: Ifc2017b40a0fb570103f0fed7bc130aa24819e9f
2017-07-17 21:39:59 +00:00

356 lines
12 KiB
JavaScript

/**
* @license
* Copyright 2016 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.
*/
describe('Player', function() {
/** @const */
var Util = shaka.test.Util;
/** @const */
var Feature = shakaAssets.Feature;
/** @type {!jasmine.Spy} */
var onErrorSpy;
/** @type {shakaExtern.SupportType} */
var support;
/** @type {!HTMLVideoElement} */
var video;
/** @type {shaka.Player} */
var player;
/** @type {shaka.util.EventManager} */
var eventManager;
var compiledShaka;
beforeAll(function(done) {
video = /** @type {!HTMLVideoElement} */ (document.createElement('video'));
video.width = 600;
video.height = 400;
video.muted = true;
document.body.appendChild(video);
var loaded = new shaka.util.PublicPromise();
if (getClientArg('uncompiled')) {
// For debugging purposes, use the uncompiled library.
compiledShaka = shaka;
loaded.resolve();
} else {
// Load the compiled library as a module.
// All tests in this suite will use the compiled library.
require(['/base/dist/shaka-player.compiled.js'], function(shakaModule) {
compiledShaka = shakaModule;
compiledShaka.net.NetworkingEngine.registerScheme(
'test', shaka.test.TestScheme);
compiledShaka.media.ManifestParser.registerParserByMime(
'application/x-test-manifest',
shaka.test.TestScheme.ManifestParser);
loaded.resolve();
});
}
loaded.then(function() {
return shaka.test.TestScheme.createManifests(compiledShaka, '_compiled');
}).then(function() {
return compiledShaka.Player.probeSupport();
}).then(function(supportResults) {
support = supportResults;
done();
});
});
beforeEach(function() {
player = new compiledShaka.Player(video);
// Grab event manager from the uncompiled library:
eventManager = new shaka.util.EventManager();
onErrorSpy = jasmine.createSpy('onError');
onErrorSpy.and.callFake(function(event) { fail(event.detail); });
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
});
afterEach(function(done) {
Promise.all([
eventManager.destroy(),
player.destroy()
]).catch(fail).then(done);
});
afterAll(function() {
document.body.removeChild(video);
});
describe('getStats', function() {
it('gives stats about current stream', function(done) {
// This is tested more in player_unit.js. This is here to test the public
// API and to check for renaming.
player.load('test:sintel_compiled').then(function() {
video.play();
return waitUntilPlayheadReaches(video, 1, 10);
}).then(function() {
var stats = player.getStats();
var expected = {
width: jasmine.any(Number),
height: jasmine.any(Number),
streamBandwidth: jasmine.any(Number),
decodedFrames: jasmine.any(Number),
droppedFrames: jasmine.any(Number),
estimatedBandwidth: jasmine.any(Number),
loadLatency: jasmine.any(Number),
playTime: jasmine.any(Number),
bufferingTime: jasmine.any(Number),
// We should have loaded the first Period by now, so we should have a
// history.
switchHistory: jasmine.arrayContaining([{
timestamp: jasmine.any(Number),
id: jasmine.any(Number),
type: 'variant',
fromAdaptation: true,
bandwidth: 0
}]),
stateHistory: jasmine.arrayContaining([{
state: 'playing',
timestamp: jasmine.any(Number),
duration: jasmine.any(Number)
}])
};
expect(stats).toEqual(expected);
}).catch(fail).then(done);
});
});
describe('setTextTrackVisibility', function() {
// Using mode='disabled' on TextTrack causes cues to go null, which leads
// to a crash in TextEngine. This validates that we do not trigger this
// behavior when changing visibility of text.
it('does not cause cues to be null', function(done) {
var textTrack = video.textTracks[0];
player.load('test:sintel_compiled').then(function() {
video.play();
return waitUntilPlayheadReaches(video, 1, 10);
}).then(function() {
// This should not be null initially.
expect(textTrack.cues).not.toBe(null);
player.setTextTrackVisibility(true);
// This should definitely not be null when visible.
expect(textTrack.cues).not.toBe(null);
player.setTextTrackVisibility(false);
// This should not transition to null when invisible.
expect(textTrack.cues).not.toBe(null);
}).catch(fail).then(done);
});
});
describe('plays', function() {
it('with external text tracks', function(done) {
player.load('test:sintel_no_text_compiled').then(function() {
// For some reason, using path-absolute URLs (i.e. without the hostname)
// like this doesn't work on Safari. So manually resolve the URL.
var locationUri = new goog.Uri(location.href);
var partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt');
var absoluteUri = locationUri.resolve(partialUri);
player.addTextTrack(absoluteUri.toString(), 'en', 'subtitles',
'text/vtt');
video.play();
return Util.delay(5);
}).then(function() {
var textTracks = player.getTextTracks();
expect(textTracks).toBeTruthy();
expect(textTracks.length).toBe(1);
expect(textTracks[0].active).toBe(true);
expect(textTracks[0].language).toEqual('en');
}).catch(fail).then(done);
});
it('while changing languages with short Periods', function(done) {
// See: https://github.com/google/shaka-player/issues/797
player.configure({preferredAudioLanguage: 'en'});
player.load('test:sintel_short_periods_compiled').then(function() {
video.play();
return waitUntilPlayheadReaches(video, 8, 30);
}).then(function() {
// The Period changes at 10 seconds. Assert that we are in the previous
// Period and have buffered into the next one.
expect(video.currentTime).toBeLessThan(9);
expect(video.buffered.end(0)).toBeGreaterThan(11);
// Change to a different language; this should clear the buffers and
// cause a Period transition again.
expect(getActiveLanguage()).toBe('en');
player.selectAudioLanguage('es');
return waitUntilPlayheadReaches(video, 21, 30);
}).then(function() {
// Should have gotten past the next Period transition and still be
// playing the new language.
expect(getActiveLanguage()).toBe('es');
}).catch(fail).then(done);
});
shakaAssets.testAssets.forEach(function(asset) {
if (asset.disabled) return;
var testName =
asset.source + ' / ' + asset.name + ' : ' + asset.manifestUri;
var wit = asset.focus ? fit : external_it;
wit(testName, function(done) {
if (asset.drm.length && !asset.drm.some(
function(keySystem) { return support.drm[keySystem]; })) {
pending('None of the required key systems are supported.');
}
var mimeTypes = [];
if (asset.features.indexOf(Feature.WEBM) >= 0)
mimeTypes.push('video/webm');
if (asset.features.indexOf(Feature.MP4) >= 0)
mimeTypes.push('video/mp4');
if (!mimeTypes.some(
function(type) { return support.media[type]; })) {
pending('None of the required MIME types are supported.');
}
var isLive = asset.features.indexOf(Feature.LIVE) >= 0;
var config = { abr: {}, drm: {}, manifest: { dash: {} } };
config.abr.enabled = false;
config.manifest.dash.clockSyncUri =
'//shaka-player-demo.appspot.com/time.txt';
if (asset.licenseServers)
config.drm.servers = asset.licenseServers;
if (asset.drmCallback)
config.manifest.dash.customScheme = asset.drmCallback;
if (asset.clearKeys)
config.drm.clearKeys = asset.clearKeys;
player.configure(config);
if (asset.licenseRequestHeaders) {
player.getNetworkingEngine().registerRequestFilter(
addLicenseRequestHeaders.bind(null, asset.licenseRequestHeaders));
}
var networkingEngine = player.getNetworkingEngine();
if (asset.requestFilter)
networkingEngine.registerRequestFilter(asset.requestFilter);
if (asset.responseFilter)
networkingEngine.registerResponseFilter(asset.responseFilter);
if (asset.extraConfig)
player.configure(asset.extraConfig);
player.load(asset.manifestUri).then(function() {
expect(player.isLive()).toEqual(isLive);
video.play();
// 30 seconds or video ended, whichever comes first.
return waitForTimeOrEnd(video, 30);
}).then(function() {
if (video.ended) {
expect(video.currentTime).toBeCloseTo(video.duration, 1);
} else {
expect(video.currentTime).toBeGreaterThan(20);
// If it were very close to duration, why !video.ended?
expect(video.currentTime).not.toBeCloseTo(video.duration);
if (!player.isLive()) {
// Seek and play out the end.
video.currentTime = video.duration - 15;
// 30 seconds or video ended, whichever comes first.
return waitForTimeOrEnd(video, 30).then(function() {
expect(video.ended).toBe(true);
expect(video.currentTime).toBeCloseTo(video.duration, 1);
});
}
}
}).catch(fail).then(done);
});
});
/**
* Gets the language of the active Variant.
* @return {string}
*/
function getActiveLanguage() {
var tracks = player.getVariantTracks().filter(function(t) {
return t.active;
});
expect(tracks.length).toBeGreaterThan(0);
return tracks[0].language;
}
});
/**
* @param {!HTMLMediaElement} video
* @param {number} playheadTime The time to wait for.
* @param {number} timeout in seconds, after which the Promise fails
* @return {!Promise}
*/
function waitUntilPlayheadReaches(video, playheadTime, timeout) {
var curEventManager = eventManager;
return new Promise(function(resolve, reject) {
curEventManager.listen(video, 'timeupdate', function() {
if (video.currentTime >= playheadTime) {
curEventManager.unlisten(video, 'timeupdate');
resolve();
}
});
Util.delay(timeout).then(function() {
curEventManager.unlisten(video, 'timeupdate');
reject('Timeout waiting for time');
});
});
}
/**
* @param {!EventTarget} target
* @param {number} timeout in seconds, after which the Promise succeeds
* @return {!Promise}
*/
function waitForTimeOrEnd(target, timeout) {
var curEventManager = eventManager;
return new Promise(function(resolve, reject) {
var callback = function() {
curEventManager.unlisten(target, 'ended');
resolve();
};
curEventManager.listen(target, 'ended', callback);
Util.delay(timeout).then(callback);
});
}
/**
* @param {!Object.<string, string>} headers
* @param {shaka.net.NetworkingEngine.RequestType} requestType
* @param {shakaExtern.Request} request
*/
function addLicenseRequestHeaders(headers, requestType, request) {
var RequestType = compiledShaka.net.NetworkingEngine.RequestType;
if (requestType != RequestType.LICENSE) return;
// Add these to the existing headers. Do not clobber them!
// For PlayReady, there will already be headers in the request.
for (var k in headers) {
request.headers[k] = headers[k];
}
}
});