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 24 matching lines...) Expand all Loading... |
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 |
| 54 // JWK routines copied from third_party/WebKit/LayoutTests/media/ |
| 55 // encrypted-media/encrypted-media-utils.js |
| 56 // |
| 57 // Encodes data (Uint8Array) into base64 string without trailing '='. |
| 58 // TODO: Update once the EME spec is updated to say base64url encoding. |
| 59 function base64Encode(data) |
| 60 { |
| 61 var result = btoa(String.fromCharCode.apply(null, data)); |
| 62 return result.replace(/=+$/g, ''); |
| 63 } |
| 64 |
| 65 // Creates a JWK from raw key ID and key. |
| 66 function createJWK(keyId, key) |
| 67 { |
| 68 var jwk = "{\"kty\":\"oct\",\"kid\":\""; |
| 69 jwk += base64Encode(keyId); |
| 70 jwk += "\",\"k\":\""; |
| 71 jwk += base64Encode(key); |
| 72 jwk += "\"}"; |
| 73 return jwk; |
| 74 } |
| 75 |
| 76 // Creates a JWK Set from an array of JWK(s). |
| 77 function createJWKSet() |
| 78 { |
| 79 var jwkSet = "{\"keys\":["; |
| 80 for (var i = 0; i < arguments.length; i++) { |
| 81 if (i != 0) |
| 82 jwkSet += ","; |
| 83 jwkSet += arguments[i]; |
| 84 } |
| 85 jwkSet += "]}"; |
| 86 return jwkSet; |
| 87 } |
| 88 |
53 function isHeartbeatMessage(msg) { | 89 function isHeartbeatMessage(msg) { |
54 return hasPrefix(msg, HEART_BEAT_HEADER); | 90 return hasPrefix(msg, HEART_BEAT_HEADER); |
55 } | 91 } |
56 | 92 |
57 function isFileIOTestMessage(msg) { | 93 function isFileIOTestMessage(msg) { |
58 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); | 94 return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); |
59 } | 95 } |
60 | 96 |
61 function loadEncryptedMediaFromURL(video) { | 97 function loadEncryptedMediaFromURL(video) { |
62 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE); | 98 return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE, |
| 99 usePrefixedEME); |
63 } | 100 } |
64 | 101 |
65 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, | 102 function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, |
66 appendSourceCallbackFn) { | 103 usePrefixedEME, appendSourceCallbackFn) { |
67 var keyRequested = false; | 104 var keyRequested = false; |
68 var sourceOpened = false; | 105 var sourceOpened = false; |
| 106 var mediaKeys; |
| 107 var mediaKeySession; |
69 // Add properties to enable verification that events occurred. | 108 // Add properties to enable verification that events occurred. |
70 video.receivedKeyAdded = false; | 109 video.receivedKeyAdded = false; |
71 video.receivedHeartbeat = false; | 110 video.receivedHeartbeat = false; |
72 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; | 111 video.isHeartbeatExpected = keySystem === EXTERNAL_CLEAR_KEY_KEY_SYSTEM; |
73 video.receivedKeyMessage = false; | 112 video.receivedKeyMessage = false; |
74 | 113 |
75 if (!(video && mediaFile && keySystem && key)) { | 114 if (!(video && mediaFile && keySystem && key)) { |
76 failTest('Missing parameters in loadEncryptedMedia().'); | 115 failTest('Missing parameters in loadEncryptedMedia().'); |
77 return; | 116 return; |
78 } | 117 } |
79 | 118 |
| 119 // Shared by prefixed and unprefixed EME. |
80 function onNeedKey(e) { | 120 function onNeedKey(e) { |
81 if (keyRequested) | 121 if (keyRequested) |
82 return; | 122 return; |
83 keyRequested = true; | 123 keyRequested = true; |
84 console.log('onNeedKey', e); | 124 console.log('onNeedKey', e); |
85 | 125 |
86 var initData = sessionToLoad ? stringToUint8Array( | 126 var initData = sessionToLoad ? stringToUint8Array( |
87 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; | 127 PREFIXED_API_LOAD_SESSION_HEADER + sessionToLoad) : e.initData; |
88 try { | 128 try { |
89 video.webkitGenerateKeyRequest(keySystem, initData); | 129 if (usePrefixedEME) { |
| 130 video.webkitGenerateKeyRequest(keySystem, initData); |
| 131 } else { |
| 132 mediaKeySession = mediaKeys.createSession(e.contentType, initData); |
| 133 mediaKeySession.addEventListener('message', onMessage); |
| 134 mediaKeySession.addEventListener('error', function() { |
| 135 setResultInTitle("KeyError"); |
| 136 }); |
| 137 mediaKeySession.addEventListener('ready', onReady); |
| 138 } |
90 } | 139 } |
91 catch(error) { | 140 catch(error) { |
92 setResultInTitle(error.name); | 141 setResultInTitle(error.name); |
93 } | 142 } |
94 } | 143 } |
95 | 144 |
| 145 // Prefixed EME callbacks. |
96 function onKeyAdded(e) { | 146 function onKeyAdded(e) { |
97 e.target.receivedKeyAdded = true; | 147 e.target.receivedKeyAdded = true; |
98 } | 148 } |
99 | 149 |
100 function onKeyMessage(message) { | 150 function onKeyMessage(message) { |
101 video.receivedKeyMessage = true; | 151 video.receivedKeyMessage = true; |
| 152 |
102 if (!message.keySystem || message.keySystem != keySystem) { | 153 if (!message.keySystem || message.keySystem != keySystem) { |
103 failTest('Message with unexpected keySystem: ' + message.keySystem); | 154 failTest('Message with unexpected keySystem: ' + message.keySystem); |
104 return; | 155 return; |
105 } | 156 } |
106 | 157 |
107 if (!message.sessionId) { | 158 if (!message.sessionId) { |
108 failTest('Message without a sessionId: ' + message.sessionId); | 159 failTest('Message without a sessionId: ' + message.sessionId); |
109 return; | 160 return; |
110 } | 161 } |
111 | 162 |
| 163 processMessage(message, message.keySystem, message.defaultURL); |
| 164 } |
| 165 |
| 166 // Unprefixed EME callbacks. |
| 167 function onReady(e) { |
| 168 } |
| 169 |
| 170 function onMessage(message) { |
| 171 processMessage(message, keySystem, message.destinationURL); |
| 172 } |
| 173 |
| 174 // Shared by prefixed and unprefixed EME. |
| 175 function processMessage(message, keySystem, url) { |
| 176 video.receivedKeyMessage = true; |
| 177 |
112 if (!message.message) { | 178 if (!message.message) { |
113 failTest('Message without a message content: ' + message.message); | 179 failTest('Message without a message content: ' + message.message); |
114 return; | 180 return; |
115 } | 181 } |
116 | 182 |
117 if (isHeartbeatMessage(message.message)) { | 183 if (isHeartbeatMessage(message.message)) { |
118 console.log('onKeyMessage - heartbeat', message); | 184 console.log('processMessage - heartbeat', message); |
119 message.target.receivedHeartbeat = true; | 185 video.receivedHeartbeat = true; |
120 verifyHeartbeatMessage(message); | 186 verifyHeartbeatMessage(keySystem, url); |
121 return; | 187 return; |
122 } | 188 } |
123 | 189 |
124 if (isFileIOTestMessage(message.message)) { | 190 if (isFileIOTestMessage(message.message)) { |
125 var success = getFileIOTestResult(message); | 191 var success = getFileIOTestResult(keySystem, message); |
126 console.log('onKeyMessage - CDM file IO test: ' + | 192 console.log('processMessage - CDM file IO test: ' + |
127 (success ? 'Success' : 'Fail')); | 193 (success ? 'Success' : 'Fail')); |
128 if (success) | 194 if (success) |
129 setResultInTitle("FILEIOTESTSUCCESS"); | 195 setResultInTitle("FILEIOTESTSUCCESS"); |
130 else | 196 else |
131 setResultInTitle("FAILED"); | 197 setResultInTitle("FAILED"); |
132 return; | 198 return; |
133 } | 199 } |
134 | 200 |
135 // For FileIOTest key system, no need to start playback. | 201 // For FileIOTest key system, no need to start playback. |
136 if (message.keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) | 202 if (keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) |
137 return; | 203 return; |
138 | 204 |
139 // No tested key system returns defaultURL in for key request messages. | 205 // No tested key system returns defaultURL in for key request messages. |
140 if (message.defaultURL) { | 206 if (url) { |
141 failTest('Message unexpectedly has defaultURL: ' + message.defaultURL); | 207 failTest('Message unexpectedly has URL: ' + url); |
142 return; | 208 return; |
143 } | 209 } |
144 | 210 |
145 // When loading a session, no need to call webkitAddKey(). | 211 // When loading a session, no need to call update()/webkitAddKey(). |
146 if (sessionToLoad) | 212 if (sessionToLoad) |
147 return; | 213 return; |
148 | 214 |
149 console.log('onKeyMessage - key request', message); | 215 console.log('processMessage - key request', message); |
150 if (forceInvalidResponse) { | 216 if (forceInvalidResponse) { |
151 console.log('Forcing an invalid onKeyMessage response.'); | 217 console.log('processMessage - Forcing an invalid response.'); |
152 var invalidData = new Uint8Array([0xAA]); | 218 var invalidData = new Uint8Array([0xAA]); |
153 video.webkitAddKey(keySystem, invalidData, invalidData); | 219 if (usePrefixedEME) { |
| 220 video.webkitAddKey(keySystem, invalidData, invalidData); |
| 221 } else { |
| 222 var jwkSet = stringToUint8Array( |
| 223 createJWKSet(createJWK(invalidData, invalidData))); |
| 224 mediaKeySession.update(jwkSet); |
| 225 } |
154 return; | 226 return; |
155 } | 227 } |
156 // Check if should send request to locally running license server. | 228 // Check if should send request to locally running license server. |
157 if (licenseServerURL) { | 229 if (licenseServerURL) { |
158 requestLicense(message); | 230 requestLicense(message); |
159 return; | 231 return; |
160 } | 232 } |
161 console.log('Respond to onKeyMessage with test key.'); | 233 console.log('processMessage - Respond with test key.'); |
162 var initData = message.message; | 234 var initData = message.message; |
163 if (mediaType.indexOf('mp4') != -1) | 235 if (mediaType.indexOf('mp4') != -1) |
164 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. | 236 initData = KEY_ID; // Temporary hack for Clear Key in v0.1. |
165 video.webkitAddKey(keySystem, key, initData); | 237 if (usePrefixedEME) { |
| 238 video.webkitAddKey(keySystem, key, initData); |
| 239 } else { |
| 240 var jwkSet = stringToUint8Array(createJWKSet(createJWK(initData, key))); |
| 241 mediaKeySession.update(jwkSet); |
| 242 } |
166 } | 243 } |
167 | 244 |
168 function verifyHeartbeatMessage(message) { | 245 function verifyHeartbeatMessage(keySystem, url) { |
169 String.prototype.startsWith = function(prefix) { | 246 String.prototype.startsWith = function(prefix) { |
170 return this.indexOf(prefix) === 0; | 247 return this.indexOf(prefix) === 0; |
171 } | 248 } |
172 | 249 |
173 function isExternalClearKey(keySystem) { | 250 function isExternalClearKey(keySystem) { |
174 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || | 251 return keySystem == EXTERNAL_CLEAR_KEY_KEY_SYSTEM || |
175 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); | 252 keySystem.startsWith(EXTERNAL_CLEAR_KEY_KEY_SYSTEM + '.'); |
176 } | 253 } |
177 | 254 |
178 // Only External Clear Key sends a HEARTBEAT message. | 255 // Only External Clear Key sends a HEARTBEAT message. |
179 if (!isExternalClearKey(message.keySystem)) { | 256 if (!isExternalClearKey(keySystem)) { |
180 failTest('Unexpected heartbeat from ' + message.keySystem); | 257 failTest('Unexpected heartbeat from ' + keySystem); |
181 return; | 258 return; |
182 } | 259 } |
183 | 260 |
184 if (message.defaultURL != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { | 261 if (url != EXTERNAL_CLEAR_KEY_HEARTBEAT_URL) { |
185 failTest('Heartbeat message with unexpected defaultURL: ' + | 262 failTest('Heartbeat message with unexpected URL: ' + url); |
186 message.defaultURL); | |
187 return; | 263 return; |
188 } | 264 } |
189 } | 265 } |
190 | 266 |
191 function getFileIOTestResult(e) { | 267 function getFileIOTestResult(keySystem, e) { |
192 // Only External Clear Key sends a FILEIOTESTRESULT message. | 268 // Only External Clear Key sends a FILEIOTESTRESULT message. |
193 if (e.keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { | 269 if (keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { |
194 failTest('Unexpected CDM file IO test result from ' + e.keySystem); | 270 failTest('Unexpected CDM file IO test result from ' + keySystem); |
195 return false; | 271 return false; |
196 } | 272 } |
197 | 273 |
198 // The test result is either '0' or '1' appended to the header. | 274 // The test result is either '0' or '1' appended to the header. |
199 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) | 275 if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) |
200 return false; | 276 return false; |
201 | 277 |
202 var result_index = FILE_IO_TEST_RESULT_HEADER.length; | 278 var result_index = FILE_IO_TEST_RESULT_HEADER.length; |
203 return String.fromCharCode(e.message[result_index]) == 1; | 279 return String.fromCharCode(e.message[result_index]) == 1; |
204 } | 280 } |
205 | 281 |
206 video.addEventListener('webkitneedkey', onNeedKey); | 282 if (usePrefixedEME) { |
207 video.addEventListener('webkitkeymessage', onKeyMessage); | 283 video.addEventListener('webkitneedkey', onNeedKey); |
208 video.addEventListener('webkitkeyerror', function() { | 284 video.addEventListener('webkitkeymessage', onKeyMessage); |
209 setResultInTitle("KeyError"); | 285 video.addEventListener('webkitkeyerror', function() { |
210 }); | 286 setResultInTitle("KeyError"); |
211 video.addEventListener('webkitkeyadded', onKeyAdded); | 287 }); |
| 288 video.addEventListener('webkitkeyadded', onKeyAdded); |
| 289 } else { |
| 290 video.addEventListener('needkey', onNeedKey); |
| 291 } |
212 installTitleEventHandler(video, 'error'); | 292 installTitleEventHandler(video, 'error'); |
213 | 293 |
214 if (useMSE) { | 294 if (useMSE) { |
215 var mediaSource = loadMediaSource(mediaFile, mediaType, | 295 var mediaSource = loadMediaSource(mediaFile, mediaType, |
216 appendSourceCallbackFn); | 296 appendSourceCallbackFn); |
217 video.src = window.URL.createObjectURL(mediaSource); | 297 video.src = window.URL.createObjectURL(mediaSource); |
218 } else { | 298 } else { |
219 video.src = mediaFile; | 299 video.src = mediaFile; |
220 } | 300 } |
| 301 if (!usePrefixedEME) { |
| 302 mediaKeys = new MediaKeys(keySystem); |
| 303 video.setMediaKeys(mediaKeys); |
| 304 } |
221 } | 305 } |
222 | 306 |
223 function getInitDataFromKeyId(keyID) { | 307 function getInitDataFromKeyId(keyID) { |
224 var init_key_id = new Uint8Array(keyID.length); | 308 var init_key_id = new Uint8Array(keyID.length); |
225 for(var i = 0; i < keyID.length; i++) { | 309 for(var i = 0; i < keyID.length; i++) { |
226 init_key_id[i] = keyID.charCodeAt(i); | 310 init_key_id[i] = keyID.charCodeAt(i); |
227 } | 311 } |
228 return init_key_id; | 312 return init_key_id; |
229 } | 313 } |
230 | 314 |
(...skipping 19 matching lines...) Expand all Loading... |
250 licenseRequestRetryDelayMs + 'ms.'); | 334 licenseRequestRetryDelayMs + 'ms.'); |
251 setTimeout(requestLicense, licenseRequestRetryDelayMs, message); | 335 setTimeout(requestLicense, licenseRequestRetryDelayMs, message); |
252 } | 336 } |
253 else | 337 else |
254 failTest('Bad license server response: ' + this.response); | 338 failTest('Bad license server response: ' + this.response); |
255 } | 339 } |
256 } | 340 } |
257 console.log('license request message', message.message); | 341 console.log('license request message', message.message); |
258 xmlhttp.send(message.message); | 342 xmlhttp.send(message.message); |
259 } | 343 } |
OLD | NEW |