250 lines
8.3 KiB
HTML
250 lines
8.3 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Test Encrypted Media Extensions - Protection Query</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
<script type="text/javascript" src="manifest.js"></script>
|
|
<script type="text/javascript" src="eme.js"></script>
|
|
</head>
|
|
<body>
|
|
<pre id="test">
|
|
<script class="testbody" type="text/javascript">
|
|
// Tests in this file check that output protection queries are performed and
|
|
// handled correctly. This is done by using a special clear key key system that
|
|
// emits key status to track protection status.
|
|
|
|
// Special key system used for these tests.
|
|
const kClearKeyWithProtectionQuery =
|
|
"org.mozilla.clearkey_with_protection_query";
|
|
const kTestFile = "bipbop-cenc-video-10s.mp4";
|
|
const kTestMimeType = 'video/mp4; codecs="avc1.4d4015"';
|
|
const kTestKeyId = "7e571d037e571d037e571d037e571d11"; // Hex representation
|
|
const kTestKey = "7e5733337e5733337e5733337e573311";
|
|
|
|
// This is the special key-id used by the mozilla clearkey CDM to signal
|
|
// protection query status. As hex it is "6f75747075742d70726f74656374696f6e",
|
|
// the hex translates to ascii "output-protection".
|
|
const kProtectionQueryKeyIdString = "output-protection";
|
|
|
|
// Options for requestMediaKeySystemAccess
|
|
const kKeySystemOptions = [
|
|
{
|
|
initDataTypes: ["cenc"],
|
|
videoCapabilities: [{ contentType: kTestMimeType }],
|
|
},
|
|
];
|
|
|
|
// Helper to setup EME on `video`.
|
|
// @param video the HTMLMediaElement to configure EME on.
|
|
// @returns a media key session for the video. Callers can use this to
|
|
// configure the `onkeystatuseschange` event handler for each test. Callers
|
|
// *should not* configure other aspects of the session as this helper already
|
|
// does so.
|
|
async function setupEme(video) {
|
|
// Start setting up EME.
|
|
let access = await navigator.requestMediaKeySystemAccess(
|
|
kClearKeyWithProtectionQuery,
|
|
kKeySystemOptions
|
|
);
|
|
let mediaKeys = await access.createMediaKeys();
|
|
await video.setMediaKeys(mediaKeys);
|
|
|
|
let session = video.mediaKeys.createSession();
|
|
|
|
video.onencrypted = async encryptedEvent => {
|
|
session.onmessage = () => {
|
|
// Handle license messages. Hard code the license because we always test
|
|
// with the same file and we know what the license should be.
|
|
const license = {
|
|
keys: [
|
|
{
|
|
kty: "oct",
|
|
kid: HexToBase64(kTestKeyId),
|
|
k: HexToBase64(kTestKey),
|
|
},
|
|
],
|
|
type: "temporary",
|
|
};
|
|
|
|
const encodedLicense = new TextEncoder().encode(JSON.stringify(license));
|
|
|
|
session.update(encodedLicense);
|
|
};
|
|
|
|
session.generateRequest(
|
|
encryptedEvent.initDataType,
|
|
encryptedEvent.initData
|
|
);
|
|
};
|
|
|
|
return session;
|
|
}
|
|
|
|
// Helper to setup MSE media on `video`.
|
|
// @param video the HTMLMediaElement to configure MSE on.
|
|
async function setupMse(video) {
|
|
const mediaSource = new MediaSource();
|
|
video.src = URL.createObjectURL(mediaSource);
|
|
await once(mediaSource, "sourceopen");
|
|
const sourceBuffer = mediaSource.addSourceBuffer("video/mp4");
|
|
let fetchResponse = await fetch(kTestFile);
|
|
sourceBuffer.appendBuffer(await fetchResponse.arrayBuffer());
|
|
await once(sourceBuffer, "updateend");
|
|
mediaSource.endOfStream();
|
|
await once(mediaSource, "sourceended");
|
|
}
|
|
|
|
// Helper to create a video element and append it to the page.
|
|
function createAndAppendVideo() {
|
|
const video = document.createElement("video");
|
|
video.id = "video";
|
|
// Loop in case tests run slowly, we want video to keep playing until we
|
|
// get expected events.
|
|
video.loop = true;
|
|
document.body.appendChild(video);
|
|
return video;
|
|
}
|
|
|
|
// Helper to remove a video from the page.
|
|
function removeVideo() {
|
|
let video = document.getElementById("video");
|
|
CleanUpMedia(video);
|
|
}
|
|
|
|
// Helper to get the status for the kProtectionQueryKeyIdString key id. A
|
|
// session can (and will) have other keys with their own status, but we want
|
|
// to check this special key to find the protection query status.
|
|
function getKeyStatusForProtectionKeyId(session) {
|
|
for (let [keyId, status] of session.keyStatuses) {
|
|
if (ArrayBufferToString(keyId) == kProtectionQueryKeyIdString) {
|
|
return status;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function getDisplayMedia() {
|
|
SpecialPowers.wrap(document).notifyUserGestureActivation();
|
|
return navigator.mediaDevices.getDisplayMedia();
|
|
}
|
|
|
|
// Tests playing encrypted media, starting a screen capture during playback,
|
|
// then stopping the capture while playback continues.
|
|
async function testProtectionQueryWithCaptureDuringVideo() {
|
|
let video = createAndAppendVideo();
|
|
|
|
// Configure the video and start it playing. KeyId should be usable (not restricted).
|
|
let session = await setupEme(video);
|
|
let keyStatusChangedPromise1 = new Promise(
|
|
resolve =>
|
|
(session.onkeystatuseschange = () => {
|
|
// We may get status changes prior to kProtectionQueryKeyIdString changing,
|
|
// ensure we wait for the first kProtectionQueryKeyIdString change.
|
|
if (getKeyStatusForProtectionKeyId(session)) {
|
|
resolve();
|
|
}
|
|
})
|
|
);
|
|
await setupMse(video);
|
|
await Promise.all([video.play(), keyStatusChangedPromise1]);
|
|
is(
|
|
getKeyStatusForProtectionKeyId(session),
|
|
"usable",
|
|
"Should be usable as capture hasn't started"
|
|
);
|
|
|
|
let keyStatusChangedPromise2 = new Promise(
|
|
resolve => (session.onkeystatuseschange = resolve)
|
|
);
|
|
let [displayMediaStream] = await Promise.all([
|
|
// Start a screen capture, this should restrict output.
|
|
getDisplayMedia(),
|
|
keyStatusChangedPromise2,
|
|
]);
|
|
is(
|
|
getKeyStatusForProtectionKeyId(session),
|
|
"output-restricted",
|
|
"Should be output-restricted as capture is happening"
|
|
);
|
|
|
|
// Stop the screen capture, output should be usable again.
|
|
let keyStatusChangedPromise3 = new Promise(
|
|
resolve => (session.onkeystatuseschange = resolve)
|
|
);
|
|
displayMediaStream.getTracks().forEach(track => track.stop());
|
|
displayMediaStream = null;
|
|
await keyStatusChangedPromise3;
|
|
is(
|
|
getKeyStatusForProtectionKeyId(session),
|
|
"usable",
|
|
"Should be usable as capture has stopped"
|
|
);
|
|
|
|
removeVideo();
|
|
}
|
|
|
|
// Tests starting a screen capture, then starting encrypted playback, then
|
|
// stopping the screen capture while encrypted playback continues.
|
|
async function testProtectionQueryWithCaptureStartingBeforeVideo() {
|
|
// Start capture before setting up video.
|
|
let displayMediaStream = await getDisplayMedia();
|
|
|
|
let video = createAndAppendVideo();
|
|
|
|
// Configure the video and start it playing. KeyId should be restricted already.
|
|
let session = await setupEme(video);
|
|
let keyStatusChangedPromise1 = new Promise(
|
|
resolve =>
|
|
(session.onkeystatuseschange = () => {
|
|
// We may get status changes prior to kProtectionQueryKeyIdString changing,
|
|
// ensure we wait for the first kProtectionQueryKeyIdString change. In
|
|
// rare cases the first protection status can be "usable" due to racing
|
|
// between playback and the machinery that detects WebRTC capture. To
|
|
// avoid this, wait for the first 'output-restricted' notification,
|
|
// which will either be the first event, or will quickly follow 'usable'.
|
|
if (getKeyStatusForProtectionKeyId(session) == "output-restricted") {
|
|
resolve();
|
|
}
|
|
})
|
|
);
|
|
await setupMse(video);
|
|
await Promise.all([video.play(), keyStatusChangedPromise1]);
|
|
is(
|
|
getKeyStatusForProtectionKeyId(session),
|
|
"output-restricted",
|
|
"Should be restricted as capture is happening"
|
|
);
|
|
|
|
// Stop the screen capture, output should be usable again.
|
|
let keyStatusChangedPromise2 = new Promise(
|
|
resolve => (session.onkeystatuseschange = resolve)
|
|
);
|
|
displayMediaStream.getTracks().forEach(track => track.stop());
|
|
displayMediaStream = null;
|
|
await keyStatusChangedPromise2;
|
|
is(
|
|
getKeyStatusForProtectionKeyId(session),
|
|
"usable",
|
|
"Should be usable as capture has stopped"
|
|
);
|
|
|
|
removeVideo();
|
|
}
|
|
|
|
add_task(async function setupEnvironment() {
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
// Need test key systems for test key system.
|
|
["media.clearkey.test-key-systems.enabled", true],
|
|
// Need relaxed navigator permissions for getDisplayMedia.
|
|
["media.navigator.permission.disabled", true],
|
|
],
|
|
});
|
|
});
|
|
add_task(testProtectionQueryWithCaptureDuringVideo);
|
|
add_task(testProtectionQueryWithCaptureStartingBeforeVideo);
|
|
</script>
|
|
</pre>
|
|
</body>
|
|
</html>
|