function testnamePrefix( qualifier, keysystem ) { return ( qualifier || '' ) + ( keysystem === 'org.w3.clearkey' ? keysystem : 'drm' ); } function getInitData(initDataType) { // FIXME: This is messed up, because here we are hard coding the key ids for the different content // that we use for clearkey testing: webm and mp4. For keyids we return the mp4 one // // The content used with the DRM today servers has a different key id altogether if (initDataType == 'webm') { return new Uint8Array([ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F ]); } if (initDataType == 'cenc') { return new Uint8Array([ 0x00, 0x00, 0x00, 0x34, // size 0x70, 0x73, 0x73, 0x68, // 'pssh' 0x01, // version = 1 0x00, 0x00, 0x00, // flags 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B, 0x00, 0x00, 0x00, 0x01, // key count 0x00, 0x00, 0x00, 0x00, 0x03, 0xd2, 0xfc, 0x41, // key id 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // datasize ]); } if (initDataType == 'keyids') { var keyId = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, 0x03, 0xd2, 0xfc, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); return stringToUint8Array(createKeyIDs(keyId)); } throw 'initDataType ' + initDataType + ' not supported.'; } function stringToUint8Array(str) { var result = new Uint8Array(str.length); for(var i = 0; i < str.length; i++) { result[i] = str.charCodeAt(i); } return result; } // Encodes |data| into base64url string. There is no '=' padding, and the // characters '-' and '_' must be used instead of '+' and '/', respectively. function base64urlEncode(data) { var result = btoa(String.fromCharCode.apply(null, data)); return result.replace(/=+$/g, '').replace(/\+/g, "-").replace(/\//g, "_"); } // Decode |encoded| using base64url decoding. function base64urlDecode(encoded) { return atob(encoded.replace(/\-/g, "+").replace(/\_/g, "/")); } // Decode |encoded| using base64 to a Uint8Array function base64DecodeToUnit8Array(encoded) { return new Uint8Array( atob( encoded ).split('').map( function(c){return c.charCodeAt(0);} ) ); } // Clear Key can also support Key IDs Initialization Data. // ref: http://w3c.github.io/encrypted-media/keyids-format.html // Each parameter is expected to be a key id in an Uint8Array. function createKeyIDs() { var keyIds = '{"kids":["'; for (var i = 0; i < arguments.length; i++) { if (i != 0) keyIds += '","'; keyIds += base64urlEncode(arguments[i]); } keyIds += '"]}'; return keyIds; } function getSupportedKeySystem() { var userAgent = navigator.userAgent.toLowerCase(); var keysystem = undefined; if (userAgent.indexOf('edge') > -1 ) { keysystem = 'com.microsoft.playready'; } else if ( userAgent.indexOf('chrome') > -1 || userAgent.indexOf('firefox') > -1 ) { keysystem = 'com.widevine.alpha'; } return keysystem; } function waitForEventAndRunStep(eventName, element, func, stepTest) { var eventCallback = function(event) { if (func) func(event); } element.addEventListener(eventName, stepTest.step_func(eventCallback), true); } function waitForEvent(eventName, element) { return new Promise(function(resolve) { element.addEventListener(eventName, resolve, true); }) } var consoleDiv = null; function consoleWrite(text) { if (!consoleDiv && document.body) { consoleDiv = document.createElement('div'); document.body.appendChild(consoleDiv); } var span = document.createElement('span'); span.appendChild(document.createTextNode(text)); span.appendChild(document.createElement('br')); consoleDiv.appendChild(span); } function forceTestFailureFromPromise(test, error, message) { test.step_func(assert_unreached)(message ? message + ': ' + error.message : error); } // Returns an array of audioCapabilities that includes entries for a set of // codecs that should cover all user agents. function getPossibleAudioCapabilities() { return [ { contentType: 'audio/mp4; codecs="mp4a.40.2"' }, { contentType: 'audio/webm; codecs="opus"' }, ]; } // Returns a trivial MediaKeySystemConfiguration that should be accepted, // possibly as a subset of the specified capabilities, by all user agents. function getSimpleConfiguration() { return [ { initDataTypes : [ 'webm', 'cenc', 'keyids' ], audioCapabilities: getPossibleAudioCapabilities() } ]; } // Returns a MediaKeySystemConfiguration for |initDataType| that should be // accepted, possibly as a subset of the specified capabilities, by all // user agents. function getSimpleConfigurationForInitDataType(initDataType) { return [ { initDataTypes: [ initDataType ], audioCapabilities: getPossibleAudioCapabilities() } ]; } // Returns a promise that is fulfilled with true if |initDataType| is supported, // by keysystem or false if not. function isInitDataTypeSupported(keysystem,initDataType) { return navigator.requestMediaKeySystemAccess( keysystem, getSimpleConfigurationForInitDataType(initDataType)) .then(function() { return true; }, function() { return false; }); } function getSupportedInitDataTypes( keysystem ) { return [ 'cenc', 'keyids', 'webm' ].filter( isInitDataTypeSupported.bind( null, keysystem ) ); } function arrayBufferAsString(buffer) { var array = []; Array.prototype.push.apply( array, new Uint8Array( buffer ) ); return '0x' + array.map( function( x ) { return x < 16 ? '0'+x.toString(16) : x.toString(16); } ).join(''); } function dumpKeyStatuses(keyStatuses,short) { var userAgent = navigator.userAgent.toLowerCase(); if (userAgent.indexOf('edge') === -1) { if (!short) { consoleWrite("for (var entry of keyStatuses)"); } for (var entry of keyStatuses) { consoleWrite(arrayBufferAsString(entry[0]) + ": " + entry[1]); } if (!short) { consoleWrite("for (var keyId of keyStatuses.keys())"); for (var keyId of keyStatuses.keys()) { consoleWrite(arrayBufferAsString(keyId)); } consoleWrite("for (var status of keyStatuses.values())"); for (var status of keyStatuses.values()) { consoleWrite(status); } consoleWrite("for (var entry of keyStatuses.entries())"); for (var entry of keyStatuses.entries()) { consoleWrite(arrayBufferAsString(entry[0]) + ": " + entry[1]); } consoleWrite("keyStatuses.forEach()"); keyStatuses.forEach(function(status, keyId) { consoleWrite(arrayBufferAsString(keyId) + ": " + status); }); } } else { if (!short) { consoleWrite("keyStatuses.forEach()"); } keyStatuses.forEach(function(keyId, status) { consoleWrite(arrayBufferAsString(keyId) + ": " + status); }); } } // Verify that |keyStatuses| contains just the keys in |keys.expected| // and none of the keys in |keys.unexpected|. All keys should have status // 'usable'. Example call: verifyKeyStatuses(mediaKeySession.keyStatuses, // { expected: [key1], unexpected: [key2] }); function verifyKeyStatuses(keyStatuses, keys) { var expected = keys.expected || []; var unexpected = keys.unexpected || []; // |keyStatuses| should have same size as number of |keys.expected|. assert_equals(keyStatuses.size, expected.length, "keystatuses should have expected size"); // All |keys.expected| should be found. expected.map(function(key) { assert_true(keyStatuses.has(key), "keystatuses should have the expected keys"); assert_equals(keyStatuses.get(key), 'usable', "keystatus value should be 'usable'"); }); // All |keys.unexpected| should not be found. unexpected.map(function(key) { assert_false(keyStatuses.has(key), "keystatuses should not have unexpected keys"); assert_equals(keyStatuses.get(key), undefined, "keystatus for unexpected key should be undefined"); }); } // This function checks that calling |testCase.func| returns a // rejected Promise with the error.name equal to // |testCase.exception|. function test_exception(testCase /*...*/) { var func = testCase.func; var exception = testCase.exception; var args = Array.prototype.slice.call(arguments, 1); // This should really be rewritten in terms of the promise_rejects_* // testharness utility functions, but that needs the async test involved // passed in, and we don't have that here. return func.apply(null, args).then( function (result) { assert_unreached(format_value(func)); }, function (error) { assert_not_equals(error.message, "", format_value(func)); // `exception` is a string name for the error. We can differentiate // JS Errors from DOMExceptions by checking whether // window[exception] exists. If it does, expectedError is the name // of a JS Error subclass and window[exception] is the constructor // for that subclass. Otherwise it's a name for a DOMException. if (window[exception]) { assert_throws_js(window[exception], () => { throw error; }, format_value(func)); } else { assert_throws_dom(exception, () => { throw error; }, format_value(func)); } } ); } // Check that the events sequence (array of strings) matches the pattern (array of either strings, or // arrays of strings, with the latter representing a possibly repeating sub-sequence) function checkEventSequence(events,pattern) { function th(i) { return i + (i < 4 ? ["th", "st", "nd", "rd"][i] : "th"); } var i = 0, j=0, k=0; while(i < events.length && j < pattern.length) { if (!Array.isArray(pattern[j])) { assert_equals(events[i], pattern[j], "Expected " + th(i+1) + " event to be '" + pattern[j] + "'"); ++i; ++j; } else { assert_equals(events[i], pattern[j][k], "Expected " + th(i+1) + " event to be '" + pattern[j][k] + "'"); ++i; k = (k+1)%pattern[j].length; if (k === 0 && events[i] !== pattern[j][0]) { ++j; } } } assert_equals(i,events.length,"Received more events than expected"); assert_equals(j,pattern.length,"Expected more events than received"); }