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 // Number of possible retries used to get a license from license server. | 13 // Number of possible retries used to get a license from license server. |
| 13 // This is used to avoid server boot up delays since there is no direct way | 14 // This is used to avoid server boot up delays since there is no direct way |
| 14 // to know if it is ready crbug.com/339289. | 15 // to know if it is ready crbug.com/339289. |
| 15 var requestLicenseTries = 3; | 16 var requestLicenseTries = 3; |
| 16 // Delay in ms between retries to get a license from license server. | 17 // Delay in ms between retries to get a license from license server. |
| 17 var licenseRequestRetryDelayMs = 3000; | 18 var licenseRequestRetryDelayMs = 3000; |
| 18 | 19 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 42 | 43 |
| 43 function hasPrefix(msg, prefix) { | 44 function hasPrefix(msg, prefix) { |
| 44 if (msg.length < prefix.length) | 45 if (msg.length < prefix.length) |
| 45 return false; | 46 return false; |
| 46 for (var i = 0; i < prefix.length; ++i) { | 47 for (var i = 0; i < prefix.length; ++i) { |
| 47 if (String.fromCharCode(msg[i]) != prefix[i]) | 48 if (String.fromCharCode(msg[i]) != prefix[i]) |
| 48 return false; | 49 return false; |
| 49 } | 50 } |
| 50 return true; | 51 return true; |
| 51 } | 52 } |
| 52 | 53 |
|
ddorwin
2014/03/04 21:18:48
Can we say that these are copied from the layout t
jrummell
2014/03/04 23:55:49
They are. Updated.
| |
| 54 // Encodes data into base64 string without trailing '='. | |
|
ddorwin
2014/03/04 21:18:48
What you really want is a base64url encoding (thou
jrummell
2014/03/04 23:55:49
Done.
| |
| 55 function base64Encode(data) | |
|
ddorwin
2014/03/04 21:18:48
|dataString|?
jrummell
2014/03/04 23:55:49
This is a Uint8Array, not a string. Updated commen
| |
| 56 { | |
| 57 var result = btoa(String.fromCharCode.apply(null, data)); | |
|
ddorwin
2014/03/04 21:18:48
What is the apply() call doing/solving?
jrummell
2014/03/04 23:55:49
It appears to be converting the Uint8Array to a st
| |
| 58 return result.replace(/=+$/g, ''); | |
| 59 } | |
| 60 | |
| 61 // Creates a JWK from raw key ID and key. | |
| 62 function createJWK(keyId, key) | |
| 63 { | |
| 64 var jwk = "{\"kty\":\"oct\",\"kid\":\""; | |
| 65 jwk += base64Encode(keyId); | |
| 66 jwk += "\",\"k\":\""; | |
| 67 jwk += base64Encode(key); | |
| 68 jwk += "\"}"; | |
| 69 return jwk; | |
| 70 } | |
| 71 | |
| 72 // Creates a JWK Set from multiple JWKs. | |
|
ddorwin
2014/03/04 21:18:48
... from an array of JWK(s).
jrummell
2014/03/04 23:55:49
Done.
| |
| 73 function createJWKSet() | |
| 74 { | |
| 75 var jwkSet = "{\"keys\":["; | |
| 76 for (var i = 0; i < arguments.length; i++) { | |
| 77 if (i != 0) | |
| 78 jwkSet += ","; | |
| 79 jwkSet += arguments[i]; | |
| 80 } | |
| 81 jwkSet += "]}"; | |
| 82 return jwkSet; | |
| 83 } | |
| 84 | |
| 53 function isHeartbeatMessage(msg) { | 85 function isHeartbeatMessage(msg) { |
| 54 return hasPrefix(msg, HEART_BEAT_HEADER); | 86 return hasPrefix(msg, HEART_BEAT_HEADER); |
| 55 } | 87 } |
| 56 | 88 |
| 57 function isFileIOTestMessage(msg) { | 89 function isFileIOTestMessage(msg) { |
| 58 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); | 90 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); |
| 59 } | 91 } |
| 60 | 92 |
| 61 function loadEncryptedMediaFromURL(video) { | 93 function loadEncryptedMediaFromURL(video) { |
| 62 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE); | 94 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE, |
| 95 usePrefixedEME); | |
| 63 } | 96 } |
| 64 | 97 |
| 65 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, | 98 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, |
| 66 appendSourceCallbackFn) { | 99 usePrefixedEME, appendSourceCallbackFn) { |
| 67 var keyRequested = false; | 100 var keyRequested = false; |
| 68 var sourceOpened = false; | 101 var sourceOpened = false; |
| 102 var mediaKeys; | |
| 103 var mediaKeySession; | |
| 69 // Add properties to enable verification that events occurred. | 104 // Add properties to enable verification that events occurred. |
| 70 video.receivedKeyAdded = false; | 105 video.receivedKeyAdded = false; |
| 71 video.receivedHeartbeat = false; | 106 video.receivedHeartbeat = false; |
| 72 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; | 107 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; |
| 73 video.receivedKeyMessage = false; | 108 video.receivedKeyMessage = false; |
| 74 | 109 |
| 75 if (!(video && mediaFile && keySystem && key)) { | 110 if (!(video && mediaFile && keySystem && key)) { |
| 76 failTest('Missing parameters in loadEncryptedMedia().'); | 111 failTest('Missing parameters in loadEncryptedMedia().'); |
| 77 return; | 112 return; |
| 78 } | 113 } |
| 79 | 114 |
| 115 // Shared by prefixed EME and EME WD | |
|
ddorwin
2014/03/04 21:18:48
ditto on "WD" in this file.
jrummell
2014/03/04 23:55:49
Done.
| |
| 80 function onNeedKey(e) { | 116 function onNeedKey(e) { |
| 81 if (keyRequested) | 117 if (keyRequested) |
| 82 return; | 118 return; |
| 83 keyRequested = true; | 119 keyRequested = true; |
| 84 console.log('onNeedKey', e); | 120 console.log('onNeedKey', e); |
| 85 | 121 |
| 86 var initData = sessionToLoad ? stringToUint8Array( | 122 var initData = sessionToLoad ? stringToUint8Array( |
| 87 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; | 123 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; |
| 88 try { | 124 try { |
| 89 video.webkitGenerateKeyRequest(keySystem, initData); | 125 if (usePrefixedEME) { |
| 126 video.webkitGenerateKeyRequest(keySystem, initData); | |
| 127 } else { | |
| 128 mediaKeySession = mediaKeys.createSession(e.contentType, initData); | |
| 129 mediaKeySession.addEventListener('message', onMessage); | |
| 130 mediaKeySession.addEventListener('error', function() { | |
| 131 setResultInTitle("KeyError"); | |
| 132 }); | |
| 133 mediaKeySession.addEventListener('ready', onReady); | |
| 134 } | |
| 90 } | 135 } |
| 91 catch(error) { | 136 catch(error) { |
| 92 setResultInTitle(error.name); | 137 setResultInTitle(error.name); |
| 93 } | 138 } |
| 94 } | 139 } |
| 95 | 140 |
| 141 // Prefixed EME callbacks | |
| 96 function onKeyAdded(e) { | 142 function onKeyAdded(e) { |
| 97 e.target.receivedKeyAdded = true; | 143 e.target.receivedKeyAdded = true; |
| 98 } | 144 } |
| 99 | 145 |
| 100 function onKeyMessage(message) { | 146 function onKeyMessage(message) { |
| 101 video.receivedKeyMessage = true; | 147 video.receivedKeyMessage = true; |
| 102 if (!message.keySystem || message.keySystem != keySystem) { | 148 if (!message.keySystem || message.keySystem != keySystem) { |
| 103 failTest('Message with unexpected keySystem: ' + message.keySystem); | 149 failTest('Message with unexpected keySystem: ' + message.keySystem); |
| 104 return; | 150 return; |
| 105 } | 151 } |
| 106 | 152 |
| 107 if (!message.sessionId) { | 153 if (!message.sessionId) { |
| 108 failTest('Message without a sessionId: ' + message.sessionId); | 154 failTest('Message without a sessionId: ' + message.sessionId); |
| 109 return; | 155 return; |
| 110 } | 156 } |
| 111 | 157 |
| 112 if (!message.message) { | 158 if (!message.message) { |
| 113 failTest('Message without a message content: ' + message.message); | 159 failTest('Message without a message content: ' + message.message); |
| 114 return; | 160 return; |
| 115 } | 161 } |
| 116 | 162 |
| 117 if (isHeartbeatMessage(message.message)) { | 163 if (isHeartbeatMessage(message.message)) { |
| 118 console.log('onKeyMessage - heartbeat', message); | 164 console.log('onKeyMessage - heartbeat', message); |
| 119 message.target.receivedHeartbeat = true; | 165 message.target.receivedHeartbeat = true; |
| 120 verifyHeartbeatMessage(message); | 166 verifyHeartbeatMessage(message.keySystem, message.defaultURL); |
| 121 return; | 167 return; |
| 122 } | 168 } |
| 123 | 169 |
| 124 if (isFileIOTestMessage(message.message)) { | 170 if (isFileIOTestMessage(message.message)) { |
| 125 var success = getFileIOTestResult(message); | 171 var success = getFileIOTestResult(message.keySystem, message); |
| 126 console.log('onKeyMessage - CDM file IO test: ' + | 172 console.log('onKeyMessage - CDM file IO test: ' + |
| 127 (success ? 'Success' : 'Fail')); | 173 (success ? 'Success' : 'Fail')); |
| 128 if (success) | 174 if (success) |
| 129 setResultInTitle("FILEIOTESTSUCCESS"); | 175 setResultInTitle("FILEIOTESTSUCCESS"); |
| 130 else | 176 else |
| 131 setResultInTitle("FAILED"); | 177 setResultInTitle("FAILED"); |
| 132 return; | 178 return; |
| 133 } | 179 } |
| 134 | 180 |
| 135 // For FileIOTest key system, no need to start playback. | 181 // For FileIOTest key system, no need to start playback. |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 158 requestLicense(message); | 204 requestLicense(message); |
| 159 return; | 205 return; |
| 160 } | 206 } |
| 161 console.log('Respond to onKeyMessage with test key.'); | 207 console.log('Respond to onKeyMessage with test key.'); |
| 162 var initData = message.message; | 208 var initData = message.message; |
| 163 if (mediaType.indexOf('mp4') != -1) | 209 if (mediaType.indexOf('mp4') != -1) |
| 164 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. | 210 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. |
| 165 video.webkitAddKey(keySystem, key, initData); | 211 video.webkitAddKey(keySystem, key, initData); |
| 166 } | 212 } |
| 167 | 213 |
| 168 function verifyHeartbeatMessage(message) { | 214 // EME WD callbacks |
| 215 function onReady(e) { | |
| 216 video.receivedKeyAdded = true; | |
|
ddorwin
2014/03/04 21:18:48
Note: This will break when we remove ready. Maybe
jrummell
2014/03/04 23:55:49
encrypted_media_player.html checks for the flag be
| |
| 217 } | |
| 218 | |
| 219 function onMessage(message) { | |
|
ddorwin
2014/03/04 21:18:48
This function has a lot of duplicate code. How muc
jrummell
2014/03/04 23:55:49
Also keySystem, update()/webKitAddkey(), using vid
| |
| 220 video.receivedKeyMessage = true; | |
| 221 | |
| 222 if (!message.message) { | |
| 223 failTest('Message without a message content: ' + message.message); | |
| 224 return; | |
| 225 } | |
| 226 | |
| 227 if (isHeartbeatMessage(message.message)) { | |
| 228 console.log('onMessage - heartbeat', message); | |
| 229 video.receivedHeartbeat = true; | |
| 230 verifyHeartbeatMessage(keySystem, message.destinationURL); | |
| 231 return; | |
| 232 } | |
| 233 | |
| 234 if (isFileIOTestMessage(message.message)) { | |
| 235 var success = getFileIOTestResult(keySystem, message); | |
| 236 console.log('onMessage - CDM file IO test: ' + | |
| 237 (success ? 'Success' : 'Fail')); | |
| 238 if (success) | |
| 239 setResultInTitle("FILEIOTESTSUCCESS"); | |
| 240 else | |
| 241 setResultInTitle("FAILED"); | |
| 242 return; | |
| 243 } | |
| 244 | |
| 245 // For FileIOTest key system, no need to start playback. | |
| 246 if (mediaKeySession.keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) | |
| 247 return; | |
| 248 | |
| 249 // No tested key system returns destinationURL in for key request messages. | |
| 250 if (message.destinationURL) { | |
| 251 failTest('Message unexpectedly has destinationURL: ' + | |
| 252 message.destinationURL); | |
| 253 return; | |
| 254 } | |
| 255 | |
| 256 // When loading a session, no need to call update(). | |
| 257 if (sessionToLoad) | |
| 258 return; | |
| 259 | |
| 260 console.log('onMessage - key request', message); | |
| 261 if (forceInvalidResponse) { | |
| 262 console.log('Forcing an invalid onMessage response.'); | |
| 263 var invalidData = new Uint8Array([0xAA]); | |
| 264 var jwkSet = stringToUint8Array( | |
| 265 createJWKSet(createJWK(invalidData, invalidData))); | |
| 266 mediaKeySession.update(jwkSet); | |
| 267 return; | |
| 268 } | |
| 269 // Check if should send request to locally running license server. | |
| 270 if (licenseServerURL) { | |
| 271 requestLicense(message); | |
| 272 return; | |
| 273 } | |
| 274 console.log('Respond to onMessage with test key.'); | |
| 275 var initData = message.message; | |
| 276 if (mediaType.indexOf('mp4') != -1) | |
| 277 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. | |
| 278 var jwkSet = stringToUint8Array(createJWKSet(createJWK(initData, key))); | |
| 279 mediaKeySession.update(jwkSet); | |
| 280 } | |
| 281 | |
|
ddorwin
2014/03/04 21:18:48
I skipped to here.
| |
| 282 function verifyHeartbeatMessage(keySystem, url) { | |
| 169 String.prototype.startsWith = function(prefix) { | 283 String.prototype.startsWith = function(prefix) { |
| 170 return this.indexOf(prefix) === 0; | 284 return this.indexOf(prefix) === 0; |
| 171 } | 285 } |
| 172 | 286 |
| 173 function isExternalClearKey(keySystem) { | 287 function isExternalClearKey(keySystem) { |
| 174 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || | 288 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || |
| 175 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); | 289 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); |
| 176 } | 290 } |
| 177 | 291 |
| 178 // Only External Clear Key sends a HEARTBEAT message. | 292 // Only External Clear Key sends a HEARTBEAT message. |
| 179 if (!isExternalClearKey(message.keySystem)) { | 293 if (!isExternalClearKey(keySystem)) { |
| 180 failTest('Unexpected heartbeat from ' + message.keySystem); | 294 failTest('Unexpected heartbeat from ' + keySystem); |
| 181 return; | 295 return; |
| 182 } | 296 } |
| 183 | 297 |
| 184 if (message.defaultURL != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { | 298 if (url != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { |
| 185 failTest('Heartbeat message with unexpected defaultURL: ' + | 299 failTest('Heartbeat message with unexpected URL: ' + url); |
| 186 message.defaultURL); | |
| 187 return; | 300 return; |
| 188 } | 301 } |
| 189 } | 302 } |
| 190 | 303 |
| 191 function getFileIOTestResult(e) { | 304 function getFileIOTestResult(keySystem, e) { |
| 192 // Only External Clear Key sends a FILEIOTESTRESULT message. | 305 // Only External Clear Key sends a FILEIOTESTRESULT message. |
| 193 if (e.keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { | 306 if (keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { |
| 194 failTest('Unexpected CDM file IO test result from ' + e.keySystem); | 307 failTest('Unexpected CDM file IO test result from ' + keySystem); |
| 195 return false; | 308 return false; |
| 196 } | 309 } |
| 197 | 310 |
| 198 // The test result is either '0' or '1' appended to the header. | 311 // The test result is either '0' or '1' appended to the header. |
| 199 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) | 312 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) |
| 200 return false; | 313 return false; |
| 201 | 314 |
| 202 var result_index = FILE_IO_TEST_RESULT_HEADER.length; | 315 var result_index = FILE_IO_TEST_RESULT_HEADER.length; |
| 203 return String.fromCharCode(e.message[result_index]) == 1; | 316 return String.fromCharCode(e.message[result_index]) == 1; |
| 204 } | 317 } |
| 205 | 318 |
| 206 video.addEventListener('webkitneedkey', onNeedKey); | 319 if (usePrefixedEME) { |
| 207 video.addEventListener('webkitkeymessage', onKeyMessage); | 320 video.addEventListener('webkitneedkey', onNeedKey); |
| 208 video.addEventListener('webkitkeyerror', function() { | 321 video.addEventListener('webkitkeymessage', onKeyMessage); |
| 209 setResultInTitle("KeyError"); | 322 video.addEventListener('webkitkeyerror', function() { |
| 210 }); | 323 setResultInTitle("KeyError"); |
| 211 video.addEventListener('webkitkeyadded', onKeyAdded); | 324 }); |
| 325 video.addEventListener('webkitkeyadded', onKeyAdded); | |
| 326 } else { | |
| 327 video.addEventListener('needkey', onNeedKey); | |
| 328 } | |
| 212 installTitleEventHandler(video, 'error'); | 329 installTitleEventHandler(video, 'error'); |
| 213 | 330 |
| 214 if (useMSE) { | 331 if (useMSE) { |
| 215 var mediaSource = loadMediaSource(mediaFile, mediaType, | 332 var mediaSource = loadMediaSource(mediaFile, mediaType, |
| 216 appendSourceCallbackFn); | 333 appendSourceCallbackFn); |
| 217 video.src = window.URL.createObjectURL(mediaSource); | 334 video.src = window.URL.createObjectURL(mediaSource); |
| 218 } else { | 335 } else { |
| 219 video.src = mediaFile; | 336 video.src = mediaFile; |
| 220 } | 337 } |
| 338 if (!usePrefixedEME) { | |
| 339 mediaKeys = new MediaKeys(keySystem); | |
|
ddorwin
2014/03/04 21:18:48
nit: Any reason not to do this at 328?
jrummell
2014/03/04 23:55:49
Unprefixed tests timeout if done at 328.
ddorwin
2014/03/05 19:04:36
Bug! Please file. I assume it is the setMediaKeys
jrummell
2014/03/05 23:13:23
Opened http://crbug.com/349546
| |
| 340 video.setMediaKeys(mediaKeys); | |
| 341 } | |
| 221 } | 342 } |
| 222 | 343 |
| 223 function getInitDataFromKeyId(keyID) { | 344 function getInitDataFromKeyId(keyID) { |
| 224 var init_key_id = new Uint8Array(keyID.length); | 345 var init_key_id = new Uint8Array(keyID.length); |
| 225 for(var i = 0; i < keyID.length; i++) { | 346 for(var i = 0; i < keyID.length; i++) { |
| 226 init_key_id[i] = keyID.charCodeAt(i); | 347 init_key_id[i] = keyID.charCodeAt(i); |
| 227 } | 348 } |
| 228 return init_key_id; | 349 return init_key_id; |
| 229 } | 350 } |
| 230 | 351 |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 250 licenseRequestRetryDelayMs + 'ms.'); | 371 licenseRequestRetryDelayMs + 'ms.'); |
| 251 setTimeout(requestLicense, licenseRequestRetryDelayMs, message); | 372 setTimeout(requestLicense, licenseRequestRetryDelayMs, message); |
| 252 } | 373 } |
| 253 else | 374 else |
| 254 failTest('Bad license server response: ' + this.response); | 375 failTest('Bad license server response: ' + this.response); |
| 255 } | 376 } |
| 256 } | 377 } |
| 257 console.log('license request message', message.message); | 378 console.log('license request message', message.message); |
| 258 xmlhttp.send(message.message); | 379 xmlhttp.send(message.message); |
| 259 } | 380 } |
| OLD | NEW |