OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
ddorwin
2014/08/22 21:08:48
Is this far too different from WMPI.cc to get any
acolwell GONE FROM CHROMIUM
2014/08/22 23:20:22
I don't know what you mean. Most of this code is s
| |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/renderer/media/crypto/encrypted_media_support_impl.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/callback_helpers.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/strings/string_number_conversions.h" | |
13 #include "base/strings/string_util.h" | |
14 #include "base/strings/utf_string_conversions.h" | |
15 #include "content/renderer/media/crypto/key_systems.h" | |
16 #include "content/renderer/media/webcontentdecryptionmodule_impl.h" | |
17 #include "content/renderer/pepper/pepper_webplugin_impl.h" | |
18 #include "media/base/bind_to_current_loop.h" | |
19 #include "third_party/WebKit/public/platform/WebContentDecryptionModule.h" | |
20 #include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h" | |
21 #include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" | |
22 #include "third_party/WebKit/public/web/WebDocument.h" | |
23 #include "third_party/WebKit/public/web/WebLocalFrame.h" | |
24 #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" | |
25 | |
26 #if defined(ENABLE_PEPPER_CDMS) | |
27 #include "content/renderer/media/crypto/pepper_cdm_wrapper_impl.h" | |
28 #endif | |
29 | |
30 using blink::WebMediaPlayer; | |
31 using blink::WebMediaPlayerClient; | |
32 using blink::WebString; | |
33 | |
34 namespace content { | |
35 | |
36 #define BIND_TO_RENDER_LOOP(function) \ | |
37 (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr()))) | |
38 | |
39 #define BIND_TO_RENDER_LOOP1(function, arg1) \ | |
40 (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr(), arg1))) | |
41 | |
42 | |
43 // Prefix for histograms related to Encrypted Media Extensions. | |
44 static const char* kMediaEme = "Media.EME."; | |
45 | |
46 // Used for calls to decryptor_ready_cb where the result can be ignored. | |
47 static void DoNothing(bool success) { | |
48 } | |
49 | |
50 // Convert a WebString to ASCII, falling back on an empty string in the case | |
51 // of a non-ASCII string. | |
52 static std::string ToASCIIOrEmpty(const WebString& string) { | |
53 return base::IsStringASCII(string) ? base::UTF16ToASCII(string) | |
54 : std::string(); | |
55 } | |
56 | |
57 // Helper functions to report media EME related stats to UMA. They follow the | |
58 // convention of more commonly used macros UMA_HISTOGRAM_ENUMERATION and | |
59 // UMA_HISTOGRAM_COUNTS. The reason that we cannot use those macros directly is | |
60 // that UMA_* macros require the names to be constant throughout the process' | |
61 // lifetime. | |
62 static void EmeUMAHistogramEnumeration(const std::string& key_system, | |
63 const std::string& method, | |
64 int sample, | |
65 int boundary_value) { | |
66 base::LinearHistogram::FactoryGet( | |
67 kMediaEme + KeySystemNameForUMA(key_system) + "." + method, | |
68 1, boundary_value, boundary_value + 1, | |
69 base::Histogram::kUmaTargetedHistogramFlag)->Add(sample); | |
70 } | |
71 | |
72 static void EmeUMAHistogramCounts(const std::string& key_system, | |
73 const std::string& method, | |
74 int sample) { | |
75 // Use the same parameters as UMA_HISTOGRAM_COUNTS. | |
76 base::Histogram::FactoryGet( | |
77 kMediaEme + KeySystemNameForUMA(key_system) + "." + method, | |
78 1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)->Add(sample); | |
79 } | |
80 | |
81 // Helper enum for reporting generateKeyRequest/addKey histograms. | |
82 enum MediaKeyException { | |
83 kUnknownResultId, | |
84 kSuccess, | |
85 kKeySystemNotSupported, | |
86 kInvalidPlayerState, | |
87 kMaxMediaKeyException | |
88 }; | |
89 | |
90 static MediaKeyException MediaKeyExceptionForUMA( | |
91 WebMediaPlayer::MediaKeyException e) { | |
92 switch (e) { | |
93 case WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported: | |
94 return kKeySystemNotSupported; | |
95 case WebMediaPlayer::MediaKeyExceptionInvalidPlayerState: | |
96 return kInvalidPlayerState; | |
97 case WebMediaPlayer::MediaKeyExceptionNoError: | |
98 return kSuccess; | |
99 default: | |
100 return kUnknownResultId; | |
101 } | |
102 } | |
103 | |
104 // Helper for converting |key_system| name and exception |e| to a pair of enum | |
105 // values from above, for reporting to UMA. | |
106 static void ReportMediaKeyExceptionToUMA(const std::string& method, | |
107 const std::string& key_system, | |
108 WebMediaPlayer::MediaKeyException e) { | |
109 MediaKeyException result_id = MediaKeyExceptionForUMA(e); | |
110 DCHECK_NE(result_id, kUnknownResultId) << e; | |
111 EmeUMAHistogramEnumeration( | |
112 key_system, method, result_id, kMaxMediaKeyException); | |
113 } | |
114 | |
115 // Guess the type of |init_data|. This is only used to handle some corner cases | |
116 // so we keep it as simple as possible without breaking major use cases. | |
117 static std::string GuessInitDataType(const unsigned char* init_data, | |
118 unsigned init_data_length) { | |
119 // Most WebM files use KeyId of 16 bytes. MP4 init data are always >16 bytes. | |
120 if (init_data_length == 16) | |
121 return "video/webm"; | |
122 | |
123 return "video/mp4"; | |
124 } | |
125 | |
126 scoped_ptr<EncryptedMediaSupport> EncryptedMediaSupport::create( | |
127 blink::WebMediaPlayerClient* client) { | |
128 return scoped_ptr<EncryptedMediaSupport>( | |
129 new EncryptedMediaSupportImpl(client)); | |
130 } | |
131 | |
132 EncryptedMediaSupportImpl::EncryptedMediaSupportImpl( | |
133 blink::WebMediaPlayerClient* client) | |
134 : client_(client), | |
135 web_cdm_(NULL) { | |
136 } | |
137 | |
138 EncryptedMediaSupportImpl::~EncryptedMediaSupportImpl() { | |
139 } | |
140 | |
141 WebMediaPlayer::MediaKeyException | |
142 EncryptedMediaSupportImpl::generateKeyRequest( | |
143 blink::WebLocalFrame* frame, | |
144 const WebString& key_system, | |
145 const unsigned char* init_data, | |
146 unsigned init_data_length) { | |
147 DVLOG(1) << "generateKeyRequest: " << base::string16(key_system) << ": " | |
148 << std::string(reinterpret_cast<const char*>(init_data), | |
149 static_cast<size_t>(init_data_length)); | |
150 | |
151 std::string ascii_key_system = | |
152 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system)); | |
153 | |
154 WebMediaPlayer::MediaKeyException e = | |
155 GenerateKeyRequestInternal(frame, ascii_key_system, init_data, | |
156 init_data_length); | |
157 ReportMediaKeyExceptionToUMA("generateKeyRequest", ascii_key_system, e); | |
158 return e; | |
159 } | |
160 | |
161 | |
162 WebMediaPlayer::MediaKeyException | |
163 EncryptedMediaSupportImpl::GenerateKeyRequestInternal( | |
164 blink::WebLocalFrame* frame, | |
165 const std::string& key_system, | |
166 const unsigned char* init_data, | |
167 unsigned init_data_length) { | |
168 if (!IsConcreteSupportedKeySystem(key_system)) | |
169 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; | |
170 | |
171 // We do not support run-time switching between key systems for now. | |
172 if (current_key_system_.empty()) { | |
173 if (!proxy_decryptor_) { | |
174 proxy_decryptor_.reset(new ProxyDecryptor( | |
175 #if defined(ENABLE_PEPPER_CDMS) | |
176 // Create() must be called synchronously as |frame| may not be | |
177 // valid afterwards. | |
178 base::Bind(&PepperCdmWrapperImpl::Create, frame), | |
179 #elif defined(ENABLE_BROWSER_CDMS) | |
180 #error Browser side CDM in WMPI for prefixed EME API not supported yet. | |
181 #endif | |
182 BIND_TO_RENDER_LOOP(&EncryptedMediaSupportImpl::OnKeyAdded), | |
183 BIND_TO_RENDER_LOOP(&EncryptedMediaSupportImpl::OnKeyError), | |
184 BIND_TO_RENDER_LOOP(&EncryptedMediaSupportImpl::OnKeyMessage))); | |
185 } | |
186 | |
187 GURL security_origin(frame->document().securityOrigin().toString()); | |
188 if (!proxy_decryptor_->InitializeCDM(key_system, security_origin)) | |
189 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; | |
190 | |
191 if (proxy_decryptor_ && !decryptor_ready_cb_.is_null()) { | |
192 base::ResetAndReturn(&decryptor_ready_cb_) | |
193 .Run(proxy_decryptor_->GetDecryptor(), base::Bind(DoNothing)); | |
194 } | |
195 | |
196 current_key_system_ = key_system; | |
197 } else if (key_system != current_key_system_) { | |
198 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState; | |
199 } | |
200 | |
201 std::string init_data_type = init_data_type_; | |
202 if (init_data_type.empty()) | |
203 init_data_type = GuessInitDataType(init_data, init_data_length); | |
204 | |
205 // TODO(xhwang): We assume all streams are from the same container (thus have | |
206 // the same "type") for now. In the future, the "type" should be passed down | |
207 // from the application. | |
208 if (!proxy_decryptor_->GenerateKeyRequest( | |
209 init_data_type, init_data, init_data_length)) { | |
210 current_key_system_.clear(); | |
211 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; | |
212 } | |
213 | |
214 return WebMediaPlayer::MediaKeyExceptionNoError; | |
215 } | |
216 | |
217 WebMediaPlayer::MediaKeyException EncryptedMediaSupportImpl::addKey( | |
218 const WebString& key_system, | |
219 const unsigned char* key, | |
220 unsigned key_length, | |
221 const unsigned char* init_data, | |
222 unsigned init_data_length, | |
223 const WebString& session_id) { | |
224 DVLOG(1) << "addKey: " << base::string16(key_system) << ": " | |
225 << std::string(reinterpret_cast<const char*>(key), | |
226 static_cast<size_t>(key_length)) << ", " | |
227 << std::string(reinterpret_cast<const char*>(init_data), | |
228 static_cast<size_t>(init_data_length)) << " [" | |
229 << base::string16(session_id) << "]"; | |
230 | |
231 std::string ascii_key_system = | |
232 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system)); | |
233 std::string ascii_session_id = ToASCIIOrEmpty(session_id); | |
234 | |
235 WebMediaPlayer::MediaKeyException e = AddKeyInternal(ascii_key_system, | |
236 key, | |
237 key_length, | |
238 init_data, | |
239 init_data_length, | |
240 ascii_session_id); | |
241 ReportMediaKeyExceptionToUMA("addKey", ascii_key_system, e); | |
242 return e; | |
243 } | |
244 | |
245 WebMediaPlayer::MediaKeyException EncryptedMediaSupportImpl::AddKeyInternal( | |
246 const std::string& key_system, | |
247 const unsigned char* key, | |
248 unsigned key_length, | |
249 const unsigned char* init_data, | |
250 unsigned init_data_length, | |
251 const std::string& session_id) { | |
252 DCHECK(key); | |
253 DCHECK_GT(key_length, 0u); | |
254 | |
255 if (!IsConcreteSupportedKeySystem(key_system)) | |
256 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; | |
257 | |
258 if (current_key_system_.empty() || key_system != current_key_system_) | |
259 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState; | |
260 | |
261 proxy_decryptor_->AddKey( | |
262 key, key_length, init_data, init_data_length, session_id); | |
263 return WebMediaPlayer::MediaKeyExceptionNoError; | |
264 } | |
265 | |
266 WebMediaPlayer::MediaKeyException EncryptedMediaSupportImpl::cancelKeyRequest( | |
267 const WebString& key_system, | |
268 const WebString& session_id) { | |
269 DVLOG(1) << "cancelKeyRequest: " << base::string16(key_system) << ": " | |
270 << " [" << base::string16(session_id) << "]"; | |
271 | |
272 std::string ascii_key_system = | |
273 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system)); | |
274 std::string ascii_session_id = ToASCIIOrEmpty(session_id); | |
275 | |
276 WebMediaPlayer::MediaKeyException e = | |
277 CancelKeyRequestInternal(ascii_key_system, ascii_session_id); | |
278 ReportMediaKeyExceptionToUMA("cancelKeyRequest", ascii_key_system, e); | |
279 return e; | |
280 } | |
281 | |
282 WebMediaPlayer::MediaKeyException | |
283 EncryptedMediaSupportImpl::CancelKeyRequestInternal( | |
284 const std::string& key_system, | |
285 const std::string& session_id) { | |
286 if (!IsConcreteSupportedKeySystem(key_system)) | |
287 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; | |
288 | |
289 if (current_key_system_.empty() || key_system != current_key_system_) | |
290 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState; | |
291 | |
292 proxy_decryptor_->CancelKeyRequest(session_id); | |
293 return WebMediaPlayer::MediaKeyExceptionNoError; | |
294 } | |
295 | |
296 void EncryptedMediaSupportImpl::setContentDecryptionModule( | |
297 blink::WebContentDecryptionModule* cdm) { | |
298 // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324 | |
299 if (!cdm) | |
300 return; | |
301 | |
302 web_cdm_ = ToWebContentDecryptionModuleImpl(cdm); | |
303 | |
304 if (web_cdm_ && !decryptor_ready_cb_.is_null()) | |
305 base::ResetAndReturn(&decryptor_ready_cb_) | |
306 .Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing)); | |
307 } | |
308 | |
309 void EncryptedMediaSupportImpl::setContentDecryptionModule( | |
310 blink::WebContentDecryptionModule* cdm, | |
311 blink::WebContentDecryptionModuleResult result) { | |
312 // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324 | |
313 if (!cdm) { | |
314 result.completeWithError( | |
315 blink::WebContentDecryptionModuleExceptionNotSupportedError, | |
316 0, | |
317 "Null MediaKeys object is not supported."); | |
318 return; | |
319 } | |
320 | |
321 web_cdm_ = ToWebContentDecryptionModuleImpl(cdm); | |
322 | |
323 if (web_cdm_ && !decryptor_ready_cb_.is_null()) { | |
324 base::ResetAndReturn(&decryptor_ready_cb_) | |
325 .Run(web_cdm_->GetDecryptor(), | |
326 BIND_TO_RENDER_LOOP1( | |
327 &EncryptedMediaSupportImpl::ContentDecryptionModuleAttached, | |
328 result)); | |
329 } else { | |
330 // No pipeline/decoder connected, so resolve the promise. When something | |
331 // is connected, setting the CDM will happen in SetDecryptorReadyCB(). | |
332 ContentDecryptionModuleAttached(result, true); | |
333 } | |
334 } | |
335 | |
336 void EncryptedMediaSupportImpl::setContentDecryptionModuleSync( | |
337 blink::WebContentDecryptionModule* cdm) { | |
338 // Used when loading media and no pipeline/decoder attached yet. | |
339 DCHECK(decryptor_ready_cb_.is_null()); | |
340 | |
341 web_cdm_ = ToWebContentDecryptionModuleImpl(cdm); | |
342 } | |
343 | |
344 void EncryptedMediaSupportImpl::ContentDecryptionModuleAttached( | |
345 blink::WebContentDecryptionModuleResult result, | |
346 bool success) { | |
347 if (success) { | |
348 result.complete(); | |
349 return; | |
350 } | |
351 | |
352 result.completeWithError( | |
353 blink::WebContentDecryptionModuleExceptionNotSupportedError, | |
354 0, | |
355 "Unable to set MediaKeys object"); | |
356 } | |
357 | |
358 media::SetDecryptorReadyCB | |
359 EncryptedMediaSupportImpl::CreateSetDecryptorReadyCB() { | |
360 return BIND_TO_RENDER_LOOP(&EncryptedMediaSupportImpl::SetDecryptorReadyCB); | |
361 } | |
362 | |
363 media::Demuxer::NeedKeyCB | |
364 EncryptedMediaSupportImpl::CreateNeedKeyCB() { | |
365 return BIND_TO_RENDER_LOOP(&EncryptedMediaSupportImpl::OnNeedKey); | |
366 } | |
367 | |
368 void EncryptedMediaSupportImpl::OnDecryptError() { | |
369 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1); | |
370 } | |
371 | |
372 void EncryptedMediaSupportImpl::OnNeedKey(const std::string& type, | |
373 const std::vector<uint8>& init_data) { | |
374 // Do not fire NeedKey event if encrypted media is not enabled. | |
375 if (!blink::WebRuntimeFeatures::isPrefixedEncryptedMediaEnabled() && | |
376 !blink::WebRuntimeFeatures::isEncryptedMediaEnabled()) { | |
377 return; | |
378 } | |
379 | |
380 UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1); | |
381 | |
382 DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_); | |
383 if (init_data_type_.empty()) | |
384 init_data_type_ = type; | |
385 | |
386 const uint8* init_data_ptr = init_data.empty() ? NULL : &init_data[0]; | |
387 client_->keyNeeded( | |
388 WebString::fromUTF8(type), init_data_ptr, init_data.size()); | |
389 } | |
390 | |
391 void EncryptedMediaSupportImpl::OnKeyAdded(const std::string& session_id) { | |
392 EmeUMAHistogramCounts(current_key_system_, "KeyAdded", 1); | |
393 client_->keyAdded( | |
394 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)), | |
395 WebString::fromUTF8(session_id)); | |
396 } | |
397 | |
398 void EncryptedMediaSupportImpl::OnKeyError(const std::string& session_id, | |
399 media::MediaKeys::KeyError error_code, | |
400 uint32 system_code) { | |
401 EmeUMAHistogramEnumeration(current_key_system_, "KeyError", | |
402 error_code, media::MediaKeys::kMaxKeyError); | |
403 | |
404 uint16 short_system_code = 0; | |
405 if (system_code > std::numeric_limits<uint16>::max()) { | |
406 LOG(WARNING) << "system_code exceeds unsigned short limit."; | |
407 short_system_code = std::numeric_limits<uint16>::max(); | |
408 } else { | |
409 short_system_code = static_cast<uint16>(system_code); | |
410 } | |
411 | |
412 client_->keyError( | |
413 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)), | |
414 WebString::fromUTF8(session_id), | |
415 static_cast<WebMediaPlayerClient::MediaKeyErrorCode>(error_code), | |
416 short_system_code); | |
417 } | |
418 | |
419 void EncryptedMediaSupportImpl::OnKeyMessage(const std::string& session_id, | |
420 const std::vector<uint8>& message, | |
421 const GURL& destination_url) { | |
422 DCHECK(destination_url.is_empty() || destination_url.is_valid()); | |
423 | |
424 client_->keyMessage( | |
425 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)), | |
426 WebString::fromUTF8(session_id), | |
427 message.empty() ? NULL : &message[0], | |
428 message.size(), | |
429 destination_url); | |
430 } | |
431 | |
432 void EncryptedMediaSupportImpl::SetDecryptorReadyCB( | |
433 const media::DecryptorReadyCB& decryptor_ready_cb) { | |
434 // Cancels the previous decryptor request. | |
435 if (decryptor_ready_cb.is_null()) { | |
436 if (!decryptor_ready_cb_.is_null()) { | |
437 base::ResetAndReturn(&decryptor_ready_cb_) | |
438 .Run(NULL, base::Bind(DoNothing)); | |
439 } | |
440 return; | |
441 } | |
442 | |
443 // TODO(xhwang): Support multiple decryptor notification request (e.g. from | |
444 // video and audio). The current implementation is okay for the current | |
445 // media pipeline since we initialize audio and video decoders in sequence. | |
446 // But WebMediaPlayerImpl should not depend on media pipeline's implementation | |
447 // detail. | |
448 DCHECK(decryptor_ready_cb_.is_null()); | |
449 | |
450 // Mixed use of prefixed and unprefixed EME APIs is disallowed by Blink. | |
451 DCHECK(!proxy_decryptor_ || !web_cdm_); | |
452 | |
453 if (proxy_decryptor_) { | |
454 decryptor_ready_cb.Run(proxy_decryptor_->GetDecryptor(), | |
455 base::Bind(DoNothing)); | |
456 return; | |
457 } | |
458 | |
459 if (web_cdm_) { | |
460 decryptor_ready_cb.Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing)); | |
461 return; | |
462 } | |
463 | |
464 decryptor_ready_cb_ = decryptor_ready_cb; | |
465 } | |
466 | |
467 } // namespace content | |
OLD | NEW |