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