Files
shaka-player/test/media/drm_engine_integration.js
T
Joey Parrish d81c9cb369 test: Deduplicate platform support check (#7450)
This deduplicates a platform support check that was run in
player_integration.js, and declares the pre-existing central support map
in an extern so we can clean up its use.

This stops a DRM integration test from timing out on FirefoxWindows. It
still gets skipped, though, due to a failing Widevine check, so there
will be follow-on work for that.

Issue #7449
2024-10-21 09:21:56 +02:00

368 lines
13 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
describe('DrmEngine', () => {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
// These come from Axinom.
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 {!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;
/** @type {shaka.extern.Stream} */
const fakeStream = shaka.test.StreamingEngineUtil.createMockVideoStream(1);
beforeAll(async () => {
video = shaka.test.UiUtils.createVideoElement();
document.body.appendChild(video);
const responses = await Promise.all([
shaka.test.Util.fetch(videoInitSegmentUri),
shaka.test.Util.fetch(videoSegmentUri),
shaka.test.Util.fetch(audioInitSegmentUri),
shaka.test.Util.fetch(audioSegmentUri),
]);
videoInitSegment = responses[0];
videoSegment = responses[1];
audioInitSegment = responses[2];
audioSegment = responses[3];
});
beforeEach(async () => {
onErrorSpy = jasmine.createSpy('onError');
onKeyStatusSpy = jasmine.createSpy('onKeyStatus');
onExpirationSpy = jasmine.createSpy('onExpirationUpdated');
onEventSpy = jasmine.createSpy('onEvent');
networkingEngine = new shaka.net.NetworkingEngine();
const 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://cwip-shaka-proxy.appspot.com/specific_key?QGCoZYh4Qmecv5GuW64ecg=/DU0CDcxDMD7U96X4ipp4A';
config.servers['com.microsoft.playready'] =
'https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(kid:4060a865-8878-4267-9cbf-91ae5bae1e72,contentkey:/DU0CDcxDMD7U96X4ipp4A==,sl:150)';
config.preferredKeySystems = [
'com.widevine.alpha',
'com.microsoft.playready',
];
drmEngine.configure(config);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.addVariant(0, (variant) => {
variant.addVideo(1, (stream) => {
stream.encrypted = true;
stream.addDrmInfo('com.microsoft.playready');
stream.addDrmInfo('com.widevine.alpha');
});
variant.addAudio(2, (stream) => {
stream.encrypted = true;
stream.addDrmInfo('com.microsoft.playready');
stream.addDrmInfo('com.widevine.alpha');
});
});
});
const videoStream = manifest.variants[0].video;
const audioStream = manifest.variants[0].audio;
eventManager = new shaka.util.EventManager();
mediaSourceEngine = new shaka.media.MediaSourceEngine(
video,
new shaka.test.FakeTextDisplayer(),
{
getKeySystem: () => null,
onMetadata: () => {},
onEvent: () => {},
});
const mediaSourceConfig =
shaka.util.PlayerConfiguration.createDefault().mediaSource;
mediaSourceEngine.configure(mediaSourceConfig);
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(() => {
document.body.removeChild(video);
});
function checkTrueDrmSupport() {
if (shaka.util.Platform.isXboxOne()) {
// Axinom won't issue a license for an Xbox One. The error message from
// the license server says "Your DRM client's security level is 150, but
// the entitlement message requires 2000 or higher."
// TODO: Stop using Axinom's license server. Use
// https://testweb.playready.microsoft.com/Server/ServiceQueryStringSyntax
return false;
}
return shakaSupport.drm['com.widevine.alpha'] ||
shakaSupport.drm['com.microsoft.playready'];
}
function checkClearKeySupport() {
return shakaSupport.drm['org.w3.clearkey'];
}
filterDescribe('basic flow', checkTrueDrmSupport, () => {
drmIt('gets a license and can play encrypted segments', async () => {
// The error callback should not be invoked.
onErrorSpy.and.callFake(fail);
const originalRequest = networkingEngine.request;
let requestComplete;
/** @type {!jasmine.Spy} */
const requestSpy = jasmine.createSpy('request');
/** @type {!shaka.util.PublicPromise} */
const requestMade = new shaka.util.PublicPromise();
requestSpy.and.callFake((...args) => {
requestMade.resolve();
// eslint-disable-next-line no-restricted-syntax
requestComplete = originalRequest.call(networkingEngine, ...args);
return requestComplete;
});
networkingEngine.request = shaka.test.Util.spyFunc(requestSpy);
/** @type {!shaka.util.PublicPromise} */
const encryptedEventSeen = new shaka.util.PublicPromise();
eventManager.listen(video, 'encrypted', () => {
encryptedEventSeen.resolve();
});
eventManager.listen(video, 'error', () => {
fail('MediaError message ' + video.error.message);
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));
}
});
/** @type {!shaka.util.PublicPromise} */
const keyStatusEventSeen = new shaka.util.PublicPromise();
onKeyStatusSpy.and.callFake(() => {
keyStatusEventSeen.resolve();
});
const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
await drmEngine.attach(video);
await mediaSourceEngine.appendBuffer(
ContentType.VIDEO, videoInitSegment, null, fakeStream,
/* hasClosedCaptions= */ false);
await mediaSourceEngine.appendBuffer(
ContentType.AUDIO, audioInitSegment, null, fakeStream,
/* hasClosedCaptions= */ false);
await encryptedEventSeen;
// 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.
await Promise.race([requestMade, keyStatusEventSeen]);
if (requestSpy.calls.count()) {
// We made a license request.
// Only one request should have been made.
expect(requestSpy).toHaveBeenCalledTimes(1);
// So it's reasonable to assume that this requestComplete Promise
// is waiting on the correct request.
await requestComplete;
} else {
// This was probably a PlayReady persistent license.
}
// Some platforms (notably 2017 Tizen TVs) do not fire key status
// events.
const keyStatusTimeout = shaka.test.Util.delay(5);
await Promise.race([keyStatusTimeout, keyStatusEventSeen]);
const call = onKeyStatusSpy.calls.mostRecent();
if (call) {
const map = /** @type {!Object} */ (call.args[0]);
expect(Object.keys(map).length).not.toBe(0);
for (const k in map) {
expect(map[k]).toBe('usable');
}
}
const reference = dummyReference(0, 10);
await mediaSourceEngine.appendBuffer(
ContentType.VIDEO, videoSegment, reference, fakeStream,
/* hasClosedCaptions= */ false);
await mediaSourceEngine.appendBuffer(
ContentType.AUDIO, audioSegment, reference, fakeStream,
/* hasClosedCaptions= */ false);
expect(video.buffered.end(0)).toBeGreaterThan(0);
await video.play();
const waiter = new shaka.test.Waiter(eventManager).timeoutAfter(15);
waiter.setMediaSourceEngine(mediaSourceEngine);
await waiter.waitForMovement(video);
// Something should have played by now.
expect(video.readyState).toBeGreaterThan(1);
expect(video.currentTime).toBeGreaterThan(0);
});
}); // describe('basic flow')
filterDescribe('ClearKey', checkClearKeySupport, () => {
drmIt('plays encrypted content with the ClearKey CDM', async () => {
// Configure DrmEngine for ClearKey playback.
const config = shaka.util.PlayerConfiguration.createDefault().drm;
config.clearKeys = {
// From https://github.com/Axinom/public-test-vectors/blob/master/README.md#v10
'4060a865887842679cbf91ae5bae1e72': 'fc35340837310cc0fb53de97e22a69e0',
};
drmEngine.configure(config);
// The error callback should not be invoked.
onErrorSpy.and.callFake(fail);
/** @type {!shaka.util.PublicPromise} */
const encryptedEventSeen = new shaka.util.PublicPromise();
eventManager.listen(video, 'encrypted', () => {
encryptedEventSeen.resolve();
});
eventManager.listen(video, 'error', () => {
fail('MediaError message ' + video.error.message);
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));
}
});
/** @type {!shaka.util.PublicPromise} */
const keyStatusEventSeen = new shaka.util.PublicPromise();
onKeyStatusSpy.and.callFake(() => {
keyStatusEventSeen.resolve();
});
const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
await drmEngine.attach(video);
await mediaSourceEngine.appendBuffer(
ContentType.VIDEO, videoInitSegment, null, fakeStream,
/* hasClosedCaptions= */ false);
await mediaSourceEngine.appendBuffer(
ContentType.AUDIO, audioInitSegment, null, fakeStream,
/* hasClosedCaptions= */ false);
await encryptedEventSeen;
// Some platforms (notably 2017 Tizen TVs) do not fire key status
// events.
const keyStatusTimeout = shaka.test.Util.delay(5);
await Promise.race([keyStatusTimeout, keyStatusEventSeen]);
const call = onKeyStatusSpy.calls.mostRecent();
if (call) {
const map = /** @type {!Object} */ (call.args[0]);
expect(Object.keys(map).length).not.toBe(0);
for (const k in map) {
expect(map[k]).toBe('usable');
}
}
const reference = dummyReference(0, 10);
await mediaSourceEngine.appendBuffer(
ContentType.VIDEO, videoSegment, reference, fakeStream,
/* hasClosedCaptions= */ false);
await mediaSourceEngine.appendBuffer(
ContentType.AUDIO, audioSegment, reference, fakeStream,
/* hasClosedCaptions= */ false);
expect(video.buffered.end(0)).toBeGreaterThan(0);
await video.play();
const waiter = new shaka.test.Waiter(eventManager).timeoutAfter(15);
waiter.setMediaSourceEngine(mediaSourceEngine);
await waiter.waitForMovement(video);
// Something should have played by now.
expect(video.readyState).toBeGreaterThan(1);
expect(video.currentTime).toBeGreaterThan(0);
});
}); // describe('ClearKey')
function dummyReference(startTime, endTime) {
return new shaka.media.SegmentReference(
startTime, endTime,
/* uris= */ () => ['foo://bar'],
/* startByte= */ 0,
/* endByte= */ null,
/* initSegmentReference= */ null,
/* timestampOffset= */ 0,
/* appendWindowStart= */ 0,
/* appendWindowEnd= */ Infinity);
}
});