Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 var keySystem = QueryString.keySystem; | 5 var keySystem = QueryString.keySystem; |
| 6 var mediaFile = QueryString.mediaFile; | 6 var mediaFile = QueryString.mediaFile; |
| 7 var mediaType = QueryString.mediaType || 'video/webm; codecs="vorbis, vp8"'; | 7 var mediaType = QueryString.mediaType || 'video/webm; codecs="vorbis, vp8"'; |
| 8 var useMSE = QueryString.useMSE == 1; | 8 var useMSE = QueryString.useMSE == 1; |
| 9 var usePrefixedEME = QueryString.usePrefixedEME == 1; | |
| 9 var forceInvalidResponse = QueryString.forceInvalidResponse == 1; | 10 var forceInvalidResponse = QueryString.forceInvalidResponse == 1; |
| 10 var sessionToLoad = QueryString.sessionToLoad; | 11 var sessionToLoad = QueryString.sessionToLoad; |
| 11 var licenseServerURL = QueryString.licenseServerURL; | 12 var licenseServerURL = QueryString.licenseServerURL; |
| 12 // Maximum license request attempts that the media element can make to get a | 13 // Maximum license request attempts that the media element can make to get a |
| 13 // valid license response from the license server. | 14 // valid license response from the license server. |
| 14 // This is used to avoid server boot up delays since there is no direct way | 15 // This is used to avoid server boot up delays since there is no direct way |
| 15 // to know if it is ready crbug.com/339289. | 16 // to know if it is ready crbug.com/339289. |
| 16 var MAX_LICENSE_REQUEST_ATTEMPTS = 3; | 17 var MAX_LICENSE_REQUEST_ATTEMPTS = 3; |
| 17 // Delay in ms between retries to get a license from license server. | 18 // Delay in ms between retries to get a license from license server. |
| 18 var LICENSE_REQUEST_RETRY_DELAY_MS = 3000; | 19 var LICENSE_REQUEST_RETRY_DELAY_MS = 3000; |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 44 function hasPrefix(msg, prefix) { | 45 function hasPrefix(msg, prefix) { |
| 45 if (msg.length < prefix.length) | 46 if (msg.length < prefix.length) |
| 46 return false; | 47 return false; |
| 47 for (var i = 0; i < prefix.length; ++i) { | 48 for (var i = 0; i < prefix.length; ++i) { |
| 48 if (String.fromCharCode(msg[i]) != prefix[i]) | 49 if (String.fromCharCode(msg[i]) != prefix[i]) |
| 49 return false; | 50 return false; |
| 50 } | 51 } |
| 51 return true; | 52 return true; |
| 52 } | 53 } |
| 53 | 54 |
| 55 // JWK routines copied from third_party/WebKit/LayoutTests/media/ | |
|
shadi1
2014/03/06 18:27:49
s/JWK/JSON Web Key (JWK)
| |
| 56 // encrypted-media/encrypted-media-utils.js | |
| 57 // | |
| 58 // Encodes data (Uint8Array) into base64 string without trailing '='. | |
| 59 // TODO: Update once the EME spec is updated to say base64url encoding. | |
|
xhwang
2014/03/06 16:36:53
TODO(jrummell)
jrummell
2014/03/06 18:26:03
Done.
| |
| 60 function base64Encode(data) | |
| 61 { | |
|
xhwang
2014/03/06 16:36:53
Nit: Layout test uses
function foo
{
}
style. B
ddorwin
2014/03/06 18:11:10
Hmm. This is copied from Blink as noted on line 55
shadi1
2014/03/06 18:18:41
I think that unless we are always keeping layout t
xhwang
2014/03/06 18:20:57
We need to keep the functionality of these two ver
jrummell
2014/03/06 18:26:03
Made consistent with rest of file. Also updated le
shadi1
2014/03/06 18:27:49
What about adding comments
// Start of copy of JW
| |
| 62 var result = btoa(String.fromCharCode.apply(null, data)); | |
| 63 return result.replace(/=+$/g, ''); | |
| 64 } | |
| 65 | |
| 66 // Creates a JWK from raw key ID and key. | |
| 67 function createJWK(keyId, key) | |
| 68 { | |
| 69 var jwk = "{\"kty\":\"oct\",\"kid\":\""; | |
| 70 jwk += base64Encode(keyId); | |
| 71 jwk += "\",\"k\":\""; | |
| 72 jwk += base64Encode(key); | |
| 73 jwk += "\"}"; | |
| 74 return jwk; | |
| 75 } | |
| 76 | |
| 77 // Creates a JWK Set from an array of JWK(s). | |
| 78 function createJWKSet() | |
| 79 { | |
| 80 var jwkSet = "{\"keys\":["; | |
| 81 for (var i = 0; i < arguments.length; i++) { | |
| 82 if (i != 0) | |
| 83 jwkSet += ","; | |
| 84 jwkSet += arguments[i]; | |
| 85 } | |
| 86 jwkSet += "]}"; | |
| 87 return jwkSet; | |
| 88 } | |
| 89 | |
| 54 function isHeartbeatMessage(msg) { | 90 function isHeartbeatMessage(msg) { |
| 55 return hasPrefix(msg, HEART_BEAT_HEADER); | 91 return hasPrefix(msg, HEART_BEAT_HEADER); |
| 56 } | 92 } |
| 57 | 93 |
| 58 function isFileIOTestMessage(msg) { | 94 function isFileIOTestMessage(msg) { |
| 59 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); | 95 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); |
| 60 } | 96 } |
| 61 | 97 |
| 62 function loadEncryptedMediaFromURL(video) { | 98 function loadEncryptedMediaFromURL(video) { |
| 63 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE); | 99 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE, |
| 100 usePrefixedEME); | |
| 64 } | 101 } |
| 65 | 102 |
| 66 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, | 103 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, |
| 67 appendSourceCallbackFn) { | 104 usePrefixedEME, appendSourceCallbackFn) { |
| 68 var keyRequested = false; | 105 var keyRequested = false; |
| 69 var sourceOpened = false; | 106 var sourceOpened = false; |
| 107 var mediaKeys; | |
| 108 var mediaKeySession; | |
| 70 // Add properties to enable verification that events occurred. | 109 // Add properties to enable verification that events occurred. |
| 71 video.receivedKeyAdded = false; | 110 video.receivedKeyAdded = false; |
| 72 video.receivedHeartbeat = false; | 111 video.receivedHeartbeat = false; |
| 73 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; | 112 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; |
| 74 video.receivedKeyMessage = false; | 113 video.receivedKeyMessage = false; |
| 75 | 114 |
| 76 if (!(video && mediaFile && keySystem && key)) { | 115 if (!(video && mediaFile && keySystem && key)) { |
| 77 failTest('Missing parameters in loadEncryptedMedia().'); | 116 failTest('Missing parameters in loadEncryptedMedia().'); |
| 78 return; | 117 return; |
| 79 } | 118 } |
| 80 | 119 |
| 120 // Shared by prefixed and unprefixed EME. | |
| 81 function onNeedKey(e) { | 121 function onNeedKey(e) { |
| 82 if (keyRequested) | 122 if (keyRequested) |
| 83 return; | 123 return; |
| 84 keyRequested = true; | 124 keyRequested = true; |
| 85 console.log('onNeedKey', e); | 125 console.log('onNeedKey', e); |
| 86 | 126 |
| 87 var initData = sessionToLoad ? stringToUint8Array( | 127 var initData = sessionToLoad ? stringToUint8Array( |
| 88 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; | 128 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; |
| 89 try { | 129 try { |
| 90 video.webkitGenerateKeyRequest(keySystem, initData); | 130 if (usePrefixedEME) { |
| 131 video.webkitGenerateKeyRequest(keySystem, initData); | |
| 132 } else { | |
| 133 mediaKeySession = mediaKeys.createSession(e.contentType, initData); | |
| 134 mediaKeySession.addEventListener('message', onMessage); | |
| 135 mediaKeySession.addEventListener('error', function() { | |
| 136 setResultInTitle("KeyError"); | |
| 137 }); | |
| 138 mediaKeySession.addEventListener('ready', onReady); | |
| 139 } | |
| 91 } | 140 } |
| 92 catch(error) { | 141 catch(error) { |
| 93 setResultInTitle(error.name); | 142 setResultInTitle(error.name); |
| 94 } | 143 } |
| 95 } | 144 } |
| 96 | 145 |
| 146 // Prefixed EME callbacks. | |
| 97 function onKeyAdded(e) { | 147 function onKeyAdded(e) { |
| 98 e.target.receivedKeyAdded = true; | 148 e.target.receivedKeyAdded = true; |
| 99 } | 149 } |
| 100 | 150 |
| 101 function onKeyMessage(message) { | 151 function onKeyMessage(message) { |
| 102 video.receivedKeyMessage = true; | 152 video.receivedKeyMessage = true; |
| 153 | |
| 103 if (!message.keySystem || message.keySystem != keySystem) { | 154 if (!message.keySystem || message.keySystem != keySystem) { |
| 104 failTest('Message with unexpected keySystem: ' + message.keySystem); | 155 failTest('Message with unexpected keySystem: ' + message.keySystem); |
| 105 return; | 156 return; |
| 106 } | 157 } |
| 107 | 158 |
| 108 if (!message.sessionId) { | 159 if (!message.sessionId) { |
| 109 failTest('Message without a sessionId: ' + message.sessionId); | 160 failTest('Message without a sessionId: ' + message.sessionId); |
| 110 return; | 161 return; |
| 111 } | 162 } |
| 112 | 163 |
| 164 processMessage(message, message.keySystem, message.defaultURL); | |
| 165 } | |
| 166 | |
| 167 // Unprefixed EME callbacks. | |
| 168 function onReady(e) { | |
| 169 } | |
| 170 | |
| 171 function onMessage(message) { | |
| 172 processMessage(message, keySystem, message.destinationURL); | |
| 173 } | |
| 174 | |
| 175 // Shared by prefixed and unprefixed EME. | |
| 176 function processMessage(message, keySystem, url) { | |
| 177 video.receivedKeyMessage = true; | |
| 178 | |
| 113 if (!message.message) { | 179 if (!message.message) { |
| 114 failTest('Message without a message content: ' + message.message); | 180 failTest('Message without a message content: ' + message.message); |
| 115 return; | 181 return; |
| 116 } | 182 } |
| 117 | 183 |
| 118 if (isHeartbeatMessage(message.message)) { | 184 if (isHeartbeatMessage(message.message)) { |
| 119 console.log('onKeyMessage - heartbeat', message); | 185 console.log('processMessage - heartbeat', message); |
| 120 message.target.receivedHeartbeat = true; | 186 video.receivedHeartbeat = true; |
| 121 verifyHeartbeatMessage(message); | 187 verifyHeartbeatMessage(keySystem, url); |
| 122 return; | 188 return; |
| 123 } | 189 } |
| 124 | 190 |
| 125 if (isFileIOTestMessage(message.message)) { | 191 if (isFileIOTestMessage(message.message)) { |
| 126 var success = getFileIOTestResult(message); | 192 var success = getFileIOTestResult(keySystem, message); |
| 127 console.log('onKeyMessage - CDM file IO test: ' + | 193 console.log('processMessage - CDM file IO test: ' + |
| 128 (success ? 'Success' : 'Fail')); | 194 (success ? 'Success' : 'Fail')); |
| 129 if (success) | 195 if (success) |
| 130 setResultInTitle("FILEIOTESTSUCCESS"); | 196 setResultInTitle("FILEIOTESTSUCCESS"); |
| 131 else | 197 else |
| 132 setResultInTitle("FAILED"); | 198 setResultInTitle("FAILED"); |
| 133 return; | 199 return; |
| 134 } | 200 } |
| 135 | 201 |
| 136 // For FileIOTest key system, no need to start playback. | 202 // For FileIOTest key system, no need to start playback. |
| 137 if (message.keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) | 203 if (keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) |
| 138 return; | 204 return; |
| 139 | 205 |
| 140 // No tested key system returns defaultURL in for key request messages. | 206 // No tested key system returns defaultURL in for key request messages. |
|
xhwang
2014/03/06 16:36:53
shadi: Is this also true for Widevine?
shadi1
2014/03/06 18:18:41
Yes. The policies used by the test license server
| |
| 141 if (message.defaultURL) { | 207 if (url) { |
| 142 failTest('Message unexpectedly has defaultURL: ' + message.defaultURL); | 208 failTest('Message unexpectedly has URL: ' + url); |
| 143 return; | 209 return; |
| 144 } | 210 } |
| 145 | 211 |
| 146 // When loading a session, no need to call webkitAddKey(). | 212 // When loading a session, no need to call update()/webkitAddKey(). |
| 147 if (sessionToLoad) | 213 if (sessionToLoad) |
| 148 return; | 214 return; |
| 149 | 215 |
| 150 console.log('onKeyMessage - key request', message); | 216 console.log('processMessage - key request', message); |
| 151 if (forceInvalidResponse) { | 217 if (forceInvalidResponse) { |
| 152 console.log('Forcing an invalid onKeyMessage response.'); | 218 console.log('processMessage - Forcing an invalid response.'); |
| 153 var invalidData = new Uint8Array([0xAA]); | 219 var invalidData = new Uint8Array([0xAA]); |
| 154 video.webkitAddKey(keySystem, invalidData, invalidData); | 220 if (usePrefixedEME) { |
| 221 video.webkitAddKey(keySystem, invalidData, invalidData); | |
| 222 } else { | |
| 223 var jwkSet = stringToUint8Array( | |
| 224 createJWKSet(createJWK(invalidData, invalidData))); | |
| 225 mediaKeySession.update(jwkSet); | |
|
xhwang
2014/03/06 16:36:53
Does the invalid response have to be a valid JWK s
jrummell
2014/03/06 18:26:03
Done.
| |
| 226 } | |
| 155 return; | 227 return; |
| 156 } | 228 } |
| 157 // Check if should send request to locally running license server. | 229 // Check if should send request to locally running license server. |
| 158 if (licenseServerURL) { | 230 if (licenseServerURL) { |
| 159 requestLicense(message); | 231 requestLicense(message); |
| 160 return; | 232 return; |
| 161 } | 233 } |
| 162 console.log('Respond to onKeyMessage with test key.'); | 234 console.log('processMessage - Respond with test key.'); |
| 163 var initData = message.message; | 235 var initData = message.message; |
| 164 if (mediaType.indexOf('mp4') != -1) | 236 if (mediaType.indexOf('mp4') != -1) |
| 165 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. | 237 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. |
| 166 video.webkitAddKey(keySystem, key, initData); | 238 if (usePrefixedEME) { |
| 239 video.webkitAddKey(keySystem, key, initData); | |
| 240 } else { | |
| 241 var jwkSet = stringToUint8Array(createJWKSet(createJWK(initData, key))); | |
| 242 mediaKeySession.update(jwkSet); | |
| 243 } | |
| 167 } | 244 } |
| 168 | 245 |
| 169 function verifyHeartbeatMessage(message) { | 246 function verifyHeartbeatMessage(keySystem, url) { |
| 170 String.prototype.startsWith = function(prefix) { | 247 String.prototype.startsWith = function(prefix) { |
| 171 return this.indexOf(prefix) === 0; | 248 return this.indexOf(prefix) === 0; |
| 172 } | 249 } |
| 173 | 250 |
| 174 function isExternalClearKey(keySystem) { | 251 function isExternalClearKey(keySystem) { |
| 175 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || | 252 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || |
| 176 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); | 253 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); |
| 177 } | 254 } |
| 178 | 255 |
| 179 // Only External Clear Key sends a HEARTBEAT message. | 256 // Only External Clear Key sends a HEARTBEAT message. |
| 180 if (!isExternalClearKey(message.keySystem)) { | 257 if (!isExternalClearKey(keySystem)) { |
| 181 failTest('Unexpected heartbeat from ' + message.keySystem); | 258 failTest('Unexpected heartbeat from ' + keySystem); |
| 182 return; | 259 return; |
| 183 } | 260 } |
| 184 | 261 |
| 185 if (message.defaultURL != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { | 262 if (url != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { |
| 186 failTest('Heartbeat message with unexpected defaultURL: ' + | 263 failTest('Heartbeat message with unexpected URL: ' + url); |
| 187 message.defaultURL); | |
| 188 return; | 264 return; |
| 189 } | 265 } |
| 190 } | 266 } |
| 191 | 267 |
| 192 function getFileIOTestResult(e) { | 268 function getFileIOTestResult(keySystem, e) { |
| 193 // Only External Clear Key sends a FILEIOTESTRESULT message. | 269 // Only External Clear Key sends a FILEIOTESTRESULT message. |
| 194 if (e.keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { | 270 if (keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { |
| 195 failTest('Unexpected CDM file IO test result from ' + e.keySystem); | 271 failTest('Unexpected CDM file IO test result from ' + keySystem); |
| 196 return false; | 272 return false; |
| 197 } | 273 } |
| 198 | 274 |
| 199 // The test result is either '0' or '1' appended to the header. | 275 // The test result is either '0' or '1' appended to the header. |
| 200 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) | 276 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) |
| 201 return false; | 277 return false; |
| 202 | 278 |
| 203 var result_index = FILE_IO_TEST_RESULT_HEADER.length; | 279 var result_index = FILE_IO_TEST_RESULT_HEADER.length; |
| 204 return String.fromCharCode(e.message[result_index]) == 1; | 280 return String.fromCharCode(e.message[result_index]) == 1; |
| 205 } | 281 } |
| 206 | 282 |
| 207 video.addEventListener('webkitneedkey', onNeedKey); | 283 if (usePrefixedEME) { |
| 208 video.addEventListener('webkitkeymessage', onKeyMessage); | 284 video.addEventListener('webkitneedkey', onNeedKey); |
| 209 video.addEventListener('webkitkeyerror', function() { | 285 video.addEventListener('webkitkeymessage', onKeyMessage); |
| 210 setResultInTitle("KeyError"); | 286 video.addEventListener('webkitkeyerror', function() { |
| 211 }); | 287 setResultInTitle("KeyError"); |
| 212 video.addEventListener('webkitkeyadded', onKeyAdded); | 288 }); |
| 289 video.addEventListener('webkitkeyadded', onKeyAdded); | |
| 290 } else { | |
| 291 video.addEventListener('needkey', onNeedKey); | |
| 292 } | |
| 213 installTitleEventHandler(video, 'error'); | 293 installTitleEventHandler(video, 'error'); |
| 214 | 294 |
| 215 if (useMSE) { | 295 if (useMSE) { |
| 216 var mediaSource = loadMediaSource(mediaFile, mediaType, | 296 var mediaSource = loadMediaSource(mediaFile, mediaType, |
| 217 appendSourceCallbackFn); | 297 appendSourceCallbackFn); |
| 218 video.src = window.URL.createObjectURL(mediaSource); | 298 video.src = window.URL.createObjectURL(mediaSource); |
| 219 } else { | 299 } else { |
| 220 video.src = mediaFile; | 300 video.src = mediaFile; |
| 221 } | 301 } |
| 302 if (!usePrefixedEME) { | |
| 303 mediaKeys = new MediaKeys(keySystem); | |
| 304 video.setMediaKeys(mediaKeys); | |
| 305 } | |
| 222 } | 306 } |
| 223 | 307 |
| 224 function getInitDataFromKeyId(keyID) { | 308 function getInitDataFromKeyId(keyID) { |
| 225 var init_key_id = new Uint8Array(keyID.length); | 309 var init_key_id = new Uint8Array(keyID.length); |
| 226 for(var i = 0; i < keyID.length; i++) { | 310 for(var i = 0; i < keyID.length; i++) { |
| 227 init_key_id[i] = keyID.charCodeAt(i); | 311 init_key_id[i] = keyID.charCodeAt(i); |
| 228 } | 312 } |
| 229 return init_key_id; | 313 return init_key_id; |
| 230 } | 314 } |
| 231 | 315 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 279 return false; | 363 return false; |
| 280 var http = new XMLHttpRequest(); | 364 var http = new XMLHttpRequest(); |
| 281 http.open('HEAD', url, false); | 365 http.open('HEAD', url, false); |
| 282 try { | 366 try { |
| 283 http.send(); | 367 http.send(); |
| 284 return http.status != 404; | 368 return http.status != 404; |
| 285 } catch (e) { | 369 } catch (e) { |
| 286 return false; | 370 return false; |
| 287 } | 371 } |
| 288 } | 372 } |
| OLD | NEW |