shaka-player
Version:
DASH/EME video player library
272 lines (240 loc) • 10.3 kB
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('DrmEngine', function() {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
// These come from Axinom and use the Axinom license server.
// TODO: Do not rely on third-party services long-term.
const videoInitSegmentUri = '/base/test/test/assets/multidrm-video-init.mp4';
const videoSegmentUri = '/base/test/test/assets/multidrm-video-segment.mp4';
const audioInitSegmentUri = '/base/test/test/assets/multidrm-audio-init.mp4';
const audioSegmentUri = '/base/test/test/assets/multidrm-audio-segment.mp4';
/** @type {!Object.<string, ?shaka.extern.DrmSupportType>} */
let support = {};
/** @type {!HTMLVideoElement} */
let video;
/** @type {shaka.extern.Manifest} */
let manifest;
/** @type {!jasmine.Spy} */
let onErrorSpy;
/** @type {!jasmine.Spy} */
let onKeyStatusSpy;
/** @type {!jasmine.Spy} */
let onExpirationSpy;
/** @type {!jasmine.Spy} */
let onEventSpy;
/** @type {!shaka.media.DrmEngine} */
let drmEngine;
/** @type {!shaka.media.MediaSourceEngine} */
let mediaSourceEngine;
/** @type {!shaka.net.NetworkingEngine} */
let networkingEngine;
/** @type {!shaka.util.EventManager} */
let eventManager;
/** @type {!ArrayBuffer} */
let videoInitSegment;
/** @type {!ArrayBuffer} */
let audioInitSegment;
/** @type {!ArrayBuffer} */
let videoSegment;
/** @type {!ArrayBuffer} */
let audioSegment;
beforeAll(async () => {
let supportTest = shaka.media.DrmEngine.probeSupport()
.then(function(result) { support = result; })
.catch(fail);
video = shaka.util.Dom.createVideoElement();
document.body.appendChild(video);
let responses = await Promise.all([
supportTest,
shaka.test.Util.fetch(videoInitSegmentUri),
shaka.test.Util.fetch(videoSegmentUri),
shaka.test.Util.fetch(audioInitSegmentUri),
shaka.test.Util.fetch(audioSegmentUri),
]);
videoInitSegment = responses[1];
videoSegment = responses[2];
audioInitSegment = responses[3];
audioSegment = responses[4];
});
beforeEach(async () => {
onErrorSpy = jasmine.createSpy('onError');
onKeyStatusSpy = jasmine.createSpy('onKeyStatus');
onExpirationSpy = jasmine.createSpy('onExpirationUpdated');
onEventSpy = jasmine.createSpy('onEvent');
networkingEngine = new shaka.net.NetworkingEngine();
networkingEngine.registerRequestFilter(function(type, request) {
if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return;
request.headers['X-AxDRM-Message'] = [
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lk',
'IjoiNjllNTQwODgtZTllMC00NTMwLThjMWEtMWViNmRjZDBkMTRlIiwibWVzc2FnZSI6e',
'yJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiNmU1YTFkMj',
'YtMjc1Ny00N2Q3LTgwNDYtZWFhNWQxZDM0YjVhIn1dfX0.yF7PflOPv9qHnu3ZWJNZ12j',
'gkqTabmwXbDWk_47tLNE',
].join('');
});
let playerInterface = {
netEngine: networkingEngine,
onError: shaka.test.Util.spyFunc(onErrorSpy),
onKeyStatus: shaka.test.Util.spyFunc(onKeyStatusSpy),
onExpirationUpdated: shaka.test.Util.spyFunc(onExpirationSpy),
onEvent: shaka.test.Util.spyFunc(onEventSpy),
};
drmEngine = new shaka.media.DrmEngine(playerInterface);
const config = shaka.util.PlayerConfiguration.createDefault().drm;
config.servers['com.widevine.alpha'] =
'https://drm-widevine-licensing.axtest.net/AcquireLicense';
config.servers['com.microsoft.playready'] =
'https://drm-playready-licensing.axtest.net/AcquireLicense';
drmEngine.configure(config);
manifest = new shaka.test.ManifestGenerator()
.addPeriod(0)
.addVariant(0)
.addDrmInfo('com.widevine.alpha')
.addDrmInfo('com.microsoft.playready')
.addVideo(1).mime('video/mp4', 'avc1.640015').encrypted(true)
.addAudio(2).mime('audio/mp4', 'mp4a.40.2').encrypted(true)
.build();
let videoStream = manifest.periods[0].variants[0].video;
let audioStream = manifest.periods[0].variants[0].audio;
eventManager = new shaka.util.EventManager();
mediaSourceEngine = new shaka.media.MediaSourceEngine(
video,
new shaka.test.FakeClosedCaptionParser(),
new shaka.test.FakeTextDisplayer());
const expectedObject = new Map();
expectedObject.set(ContentType.AUDIO, audioStream);
expectedObject.set(ContentType.VIDEO, videoStream);
await mediaSourceEngine.init(expectedObject, false);
});
afterEach(async () => {
eventManager.release();
await mediaSourceEngine.destroy();
await networkingEngine.destroy();
await drmEngine.destroy();
});
afterAll(function() {
document.body.removeChild(video);
});
describe('basic flow', function() {
drmIt('gets a license and can play encrypted segments',
checkAndRun((done) => {
// The error callback should not be invoked.
onErrorSpy.and.callFake(fail);
let originalRequest = networkingEngine.request.bind(networkingEngine);
let requestComplete;
let requestSpy = jasmine.createSpy('request');
let requestMade = new shaka.util.PublicPromise();
requestSpy.and.callFake(function() {
requestMade.resolve();
requestComplete = originalRequest.apply(null, arguments);
return requestComplete;
});
networkingEngine.request = shaka.test.Util.spyFunc(requestSpy);
let encryptedEventSeen = new shaka.util.PublicPromise();
eventManager.listen(video, 'encrypted', function() {
encryptedEventSeen.resolve();
});
eventManager.listen(video, 'error', function() {
fail('MediaError code ' + video.error.code);
let extended = video.error.msExtendedCode;
if (extended) {
if (extended < 0) {
extended += Math.pow(2, 32);
}
fail('MediaError msExtendedCode ' + extended.toString(16));
}
});
let keyStatusEventSeen = new shaka.util.PublicPromise();
onKeyStatusSpy.and.callFake(function() {
keyStatusEventSeen.resolve();
});
const periods = manifest.periods;
const variants = shaka.util.Periods.getAllVariantsFrom(periods);
drmEngine.initForPlayback(
variants, manifest.offlineSessionIds).then(function() {
return drmEngine.attach(video);
}).then(function() {
return mediaSourceEngine.appendBuffer(ContentType.VIDEO,
videoInitSegment, null, null, /* hasClosedCaptions */ false);
}).then(function() {
return mediaSourceEngine.appendBuffer(ContentType.AUDIO,
audioInitSegment, null, null, /* hasClosedCaptions */ false);
}).then(function() {
return encryptedEventSeen;
}).then(function() {
// With PlayReady, a persistent license policy can cause a different
// chain of events. In particular, the request is bypassed and we
// get a usable key right away.
return Promise.race([requestMade, keyStatusEventSeen]);
}).then(function() {
if (requestSpy.calls.count()) {
// We made a license request.
// Only one request should have been made.
expect(requestSpy.calls.count()).toBe(1);
// So it's reasonable to assume that this requestComplete Promise
// is waiting on the correct request.
return requestComplete;
} else {
// This was probably a PlayReady persistent license.
}
}).then(function() {
// Some platforms (notably 2017 Tizen TVs) do not fire key status
// events.
let keyStatusTimeout = shaka.test.Util.delay(5);
return Promise.race([keyStatusTimeout, keyStatusEventSeen]);
}).then(function() {
let call = onKeyStatusSpy.calls.mostRecent();
if (call) {
let map = /** @type {!Object} */ (call.args[0]);
expect(Object.keys(map).length).not.toBe(0);
for (let k in map) {
expect(map[k]).toBe('usable');
}
}
return mediaSourceEngine.appendBuffer(ContentType.VIDEO,
videoSegment, null, null, /* hasClosedCaptions */ false);
}).then(function() {
return mediaSourceEngine.appendBuffer(ContentType.AUDIO,
audioSegment, null, null, /* hasClosedCaptions */ false);
}).then(function() {
expect(video.buffered.end(0)).toBeGreaterThan(0);
video.play();
const waiter = new shaka.test.Waiter(eventManager).timeoutAfter(15);
return waiter.waitForMovement(video);
}).then(function() {
// Something should have played by now.
expect(video.readyState).toBeGreaterThan(1);
expect(video.currentTime).toBeGreaterThan(0);
}).catch(fail).then(done);
}));
}); // describe('basic flow')
/**
* Before running the test, check if the appropriate keysystems are available.
* @param {function(function())} test
* @return {function(function())}
*/
function checkAndRun(test) {
return function(done) {
if (!support['com.widevine.alpha'] &&
!support['com.microsoft.playready']) {
pending('Skipping DrmEngine tests.');
} else {
test(done);
}
};
}
});