1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/media/crypto/encrypted_media_player_support_impl.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/crypto/render_cdm_factory.h"
17 #include "content/renderer/media/webcontentdecryptionmodule_impl.h"
18 #include "media/base/bind_to_current_loop.h"
19 #include "media/blink/encrypted_media_player_support.h"
20 #include "third_party/WebKit/public/platform/WebContentDecryptionModule.h"
21 #include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h"
22 #include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
23 #include "third_party/WebKit/public/web/WebDocument.h"
24 #include "third_party/WebKit/public/web/WebLocalFrame.h"
25 #include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
27 #if defined(ENABLE_PEPPER_CDMS)
28 #include "content/renderer/media/crypto/pepper_cdm_wrapper_impl.h"
29 #elif defined(ENABLE_BROWSER_CDMS)
30 #error Browser side CDM in WMPI for prefixed EME API not supported yet.
33 using blink::WebMediaPlayer;
34 using blink::WebMediaPlayerClient;
35 using blink::WebString;
39 #define BIND_TO_RENDER_LOOP(function) \
40 (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr())))
42 #define BIND_TO_RENDER_LOOP1(function, arg1) \
43 (media::BindToCurrentLoop(base::Bind(function, AsWeakPtr(), arg1)))
45 // Prefix for histograms related to Encrypted Media Extensions.
46 static const char* kMediaEme = "Media.EME.";
48 // Used for calls to decryptor_ready_cb where the result can be ignored.
49 static void DoNothing(bool success) {
52 // Convert a WebString to ASCII, falling back on an empty string in the case
53 // of a non-ASCII string.
54 static std::string ToASCIIOrEmpty(const WebString& string) {
55 return base::IsStringASCII(string) ? base::UTF16ToASCII(string)
59 // Helper functions to report media EME related stats to UMA. They follow the
60 // convention of more commonly used macros UMA_HISTOGRAM_ENUMERATION and
61 // UMA_HISTOGRAM_COUNTS. The reason that we cannot use those macros directly is
62 // that UMA_* macros require the names to be constant throughout the process'
64 static void EmeUMAHistogramEnumeration(const std::string& key_system,
65 const std::string& method,
68 base::LinearHistogram::FactoryGet(
69 kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
70 1, boundary_value, boundary_value + 1,
71 base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
74 static void EmeUMAHistogramCounts(const std::string& key_system,
75 const std::string& method,
77 // Use the same parameters as UMA_HISTOGRAM_COUNTS.
78 base::Histogram::FactoryGet(
79 kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
80 1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
83 // Helper enum for reporting generateKeyRequest/addKey histograms.
84 enum MediaKeyException {
87 kKeySystemNotSupported,
92 static MediaKeyException MediaKeyExceptionForUMA(
93 WebMediaPlayer::MediaKeyException e) {
95 case WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported:
96 return kKeySystemNotSupported;
97 case WebMediaPlayer::MediaKeyExceptionInvalidPlayerState:
98 return kInvalidPlayerState;
99 case WebMediaPlayer::MediaKeyExceptionNoError:
102 return kUnknownResultId;
106 // Helper for converting |key_system| name and exception |e| to a pair of enum
107 // values from above, for reporting to UMA.
108 static void ReportMediaKeyExceptionToUMA(const std::string& method,
109 const std::string& key_system,
110 WebMediaPlayer::MediaKeyException e) {
111 MediaKeyException result_id = MediaKeyExceptionForUMA(e);
112 DCHECK_NE(result_id, kUnknownResultId) << e;
113 EmeUMAHistogramEnumeration(
114 key_system, method, result_id, kMaxMediaKeyException);
117 // Guess the type of |init_data|. This is only used to handle some corner cases
118 // so we keep it as simple as possible without breaking major use cases.
119 static std::string GuessInitDataType(const unsigned char* init_data,
120 unsigned init_data_length) {
121 // Most WebM files use KeyId of 16 bytes. CENC init data is always >16 bytes.
122 if (init_data_length == 16)
128 scoped_ptr<media::EncryptedMediaPlayerSupport>
129 EncryptedMediaPlayerSupportImpl::Create(blink::WebMediaPlayerClient* client) {
130 return scoped_ptr<EncryptedMediaPlayerSupport>(
131 new EncryptedMediaPlayerSupportImpl(client));
134 EncryptedMediaPlayerSupportImpl::EncryptedMediaPlayerSupportImpl(
135 blink::WebMediaPlayerClient* client)
140 EncryptedMediaPlayerSupportImpl::~EncryptedMediaPlayerSupportImpl() {
143 WebMediaPlayer::MediaKeyException
144 EncryptedMediaPlayerSupportImpl::GenerateKeyRequest(
145 blink::WebLocalFrame* frame,
146 const WebString& key_system,
147 const unsigned char* init_data,
148 unsigned init_data_length) {
149 DVLOG(1) << "generateKeyRequest: " << base::string16(key_system) << ": "
150 << std::string(reinterpret_cast<const char*>(init_data),
151 static_cast<size_t>(init_data_length));
153 std::string ascii_key_system =
154 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
156 WebMediaPlayer::MediaKeyException e =
157 GenerateKeyRequestInternal(frame, ascii_key_system, init_data,
159 ReportMediaKeyExceptionToUMA("generateKeyRequest", ascii_key_system, e);
163 WebMediaPlayer::MediaKeyException
164 EncryptedMediaPlayerSupportImpl::GenerateKeyRequestInternal(
165 blink::WebLocalFrame* frame,
166 const std::string& key_system,
167 const unsigned char* init_data,
168 unsigned init_data_length) {
169 if (!IsConcreteSupportedKeySystem(key_system))
170 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
172 // We do not support run-time switching between key systems for now.
173 if (current_key_system_.empty()) {
174 if (!proxy_decryptor_) {
175 proxy_decryptor_.reset(new ProxyDecryptor(
176 BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyAdded),
177 BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyError),
178 BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnKeyMessage)));
181 GURL security_origin(frame->document().securityOrigin().toString());
183 #if defined(ENABLE_PEPPER_CDMS)
184 RenderCdmFactory cdm_factory(
185 base::Bind(&PepperCdmWrapperImpl::Create, frame));
187 RenderCdmFactory cdm_factory;
190 if (!proxy_decryptor_->InitializeCDM(&cdm_factory, key_system,
192 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
195 if (proxy_decryptor_ && !decryptor_ready_cb_.is_null()) {
196 base::ResetAndReturn(&decryptor_ready_cb_)
197 .Run(proxy_decryptor_->GetDecryptor(), base::Bind(DoNothing));
200 current_key_system_ = key_system;
201 } else if (key_system != current_key_system_) {
202 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
205 std::string init_data_type = init_data_type_;
206 if (init_data_type.empty())
207 init_data_type = GuessInitDataType(init_data, init_data_length);
209 // TODO(xhwang): We assume all streams are from the same container (thus have
210 // the same "type") for now. In the future, the "type" should be passed down
211 // from the application.
212 if (!proxy_decryptor_->GenerateKeyRequest(
213 init_data_type, init_data, init_data_length)) {
214 current_key_system_.clear();
215 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
218 return WebMediaPlayer::MediaKeyExceptionNoError;
221 WebMediaPlayer::MediaKeyException EncryptedMediaPlayerSupportImpl::AddKey(
222 const WebString& key_system,
223 const unsigned char* key,
225 const unsigned char* init_data,
226 unsigned init_data_length,
227 const WebString& session_id) {
228 DVLOG(1) << "addKey: " << base::string16(key_system) << ": "
229 << std::string(reinterpret_cast<const char*>(key),
230 static_cast<size_t>(key_length)) << ", "
231 << std::string(reinterpret_cast<const char*>(init_data),
232 static_cast<size_t>(init_data_length)) << " ["
233 << base::string16(session_id) << "]";
235 std::string ascii_key_system =
236 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
237 std::string ascii_session_id = ToASCIIOrEmpty(session_id);
239 WebMediaPlayer::MediaKeyException e = AddKeyInternal(ascii_key_system,
245 ReportMediaKeyExceptionToUMA("addKey", ascii_key_system, e);
249 WebMediaPlayer::MediaKeyException
250 EncryptedMediaPlayerSupportImpl::AddKeyInternal(
251 const std::string& key_system,
252 const unsigned char* key,
254 const unsigned char* init_data,
255 unsigned init_data_length,
256 const std::string& session_id) {
258 DCHECK_GT(key_length, 0u);
260 if (!IsConcreteSupportedKeySystem(key_system))
261 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
263 if (current_key_system_.empty() || key_system != current_key_system_)
264 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
266 proxy_decryptor_->AddKey(
267 key, key_length, init_data, init_data_length, session_id);
268 return WebMediaPlayer::MediaKeyExceptionNoError;
271 WebMediaPlayer::MediaKeyException
272 EncryptedMediaPlayerSupportImpl::CancelKeyRequest(
273 const WebString& key_system,
274 const WebString& session_id) {
275 DVLOG(1) << "cancelKeyRequest: " << base::string16(key_system) << ": "
276 << " [" << base::string16(session_id) << "]";
278 std::string ascii_key_system =
279 GetUnprefixedKeySystemName(ToASCIIOrEmpty(key_system));
280 std::string ascii_session_id = ToASCIIOrEmpty(session_id);
282 WebMediaPlayer::MediaKeyException e =
283 CancelKeyRequestInternal(ascii_key_system, ascii_session_id);
284 ReportMediaKeyExceptionToUMA("cancelKeyRequest", ascii_key_system, e);
288 WebMediaPlayer::MediaKeyException
289 EncryptedMediaPlayerSupportImpl::CancelKeyRequestInternal(
290 const std::string& key_system,
291 const std::string& session_id) {
292 if (!IsConcreteSupportedKeySystem(key_system))
293 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
295 if (current_key_system_.empty() || key_system != current_key_system_)
296 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
298 proxy_decryptor_->CancelKeyRequest(session_id);
299 return WebMediaPlayer::MediaKeyExceptionNoError;
302 void EncryptedMediaPlayerSupportImpl::SetInitialContentDecryptionModule(
303 blink::WebContentDecryptionModule* initial_cdm) {
304 // Used when loading media and no pipeline/decoder attached yet.
305 DCHECK(decryptor_ready_cb_.is_null());
307 web_cdm_ = ToWebContentDecryptionModuleImpl(initial_cdm);
310 void EncryptedMediaPlayerSupportImpl::SetContentDecryptionModule(
311 blink::WebContentDecryptionModule* cdm) {
312 // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324
316 web_cdm_ = ToWebContentDecryptionModuleImpl(cdm);
318 if (web_cdm_ && !decryptor_ready_cb_.is_null())
319 base::ResetAndReturn(&decryptor_ready_cb_)
320 .Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing));
323 void EncryptedMediaPlayerSupportImpl::SetContentDecryptionModule(
324 blink::WebContentDecryptionModule* cdm,
325 blink::WebContentDecryptionModuleResult result) {
326 // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324
328 result.completeWithError(
329 blink::WebContentDecryptionModuleExceptionNotSupportedError,
331 "Null MediaKeys object is not supported.");
335 web_cdm_ = ToWebContentDecryptionModuleImpl(cdm);
337 if (web_cdm_ && !decryptor_ready_cb_.is_null()) {
338 base::ResetAndReturn(&decryptor_ready_cb_)
339 .Run(web_cdm_->GetDecryptor(), BIND_TO_RENDER_LOOP1(
340 &EncryptedMediaPlayerSupportImpl::ContentDecryptionModuleAttached,
343 // No pipeline/decoder connected, so resolve the promise. When something
344 // is connected, setting the CDM will happen in SetDecryptorReadyCB().
345 ContentDecryptionModuleAttached(result, true);
349 void EncryptedMediaPlayerSupportImpl::ContentDecryptionModuleAttached(
350 blink::WebContentDecryptionModuleResult result,
357 result.completeWithError(
358 blink::WebContentDecryptionModuleExceptionNotSupportedError,
360 "Unable to set MediaKeys object");
363 media::SetDecryptorReadyCB
364 EncryptedMediaPlayerSupportImpl::CreateSetDecryptorReadyCB() {
365 return BIND_TO_RENDER_LOOP(
366 &EncryptedMediaPlayerSupportImpl::SetDecryptorReadyCB);
369 media::Demuxer::NeedKeyCB
370 EncryptedMediaPlayerSupportImpl::CreateNeedKeyCB() {
371 return BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnNeedKey);
374 void EncryptedMediaPlayerSupportImpl::OnPipelineDecryptError() {
375 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1);
378 void EncryptedMediaPlayerSupportImpl::OnNeedKey(const std::string& type,
379 const std::vector<uint8>& init_data) {
380 // Do not fire NeedKey event if encrypted media is not enabled.
381 if (!blink::WebRuntimeFeatures::isPrefixedEncryptedMediaEnabled() &&
382 !blink::WebRuntimeFeatures::isEncryptedMediaEnabled()) {
386 UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1);
388 DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_);
389 if (init_data_type_.empty())
390 init_data_type_ = type;
392 const uint8* init_data_ptr = init_data.empty() ? NULL : &init_data[0];
394 WebString::fromUTF8(type), init_data_ptr, init_data.size());
397 void EncryptedMediaPlayerSupportImpl::OnKeyAdded(
398 const std::string& session_id) {
399 EmeUMAHistogramCounts(current_key_system_, "KeyAdded", 1);
401 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
402 WebString::fromUTF8(session_id));
405 void EncryptedMediaPlayerSupportImpl::OnKeyError(const std::string& session_id,
406 media::MediaKeys::KeyError error_code,
407 uint32 system_code) {
408 EmeUMAHistogramEnumeration(current_key_system_, "KeyError",
409 error_code, media::MediaKeys::kMaxKeyError);
411 uint16 short_system_code = 0;
412 if (system_code > std::numeric_limits<uint16>::max()) {
413 LOG(WARNING) << "system_code exceeds unsigned short limit.";
414 short_system_code = std::numeric_limits<uint16>::max();
416 short_system_code = static_cast<uint16>(system_code);
420 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
421 WebString::fromUTF8(session_id),
422 static_cast<WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
426 void EncryptedMediaPlayerSupportImpl::OnKeyMessage(
427 const std::string& session_id,
428 const std::vector<uint8>& message,
429 const GURL& destination_url) {
430 DCHECK(destination_url.is_empty() || destination_url.is_valid());
433 WebString::fromUTF8(GetPrefixedKeySystemName(current_key_system_)),
434 WebString::fromUTF8(session_id),
435 message.empty() ? NULL : &message[0],
440 void EncryptedMediaPlayerSupportImpl::SetDecryptorReadyCB(
441 const media::DecryptorReadyCB& decryptor_ready_cb) {
442 // Cancels the previous decryptor request.
443 if (decryptor_ready_cb.is_null()) {
444 if (!decryptor_ready_cb_.is_null()) {
445 base::ResetAndReturn(&decryptor_ready_cb_)
446 .Run(NULL, base::Bind(DoNothing));
451 // TODO(xhwang): Support multiple decryptor notification request (e.g. from
452 // video and audio). The current implementation is okay for the current
453 // media pipeline since we initialize audio and video decoders in sequence.
454 // But WebMediaPlayerImpl should not depend on media pipeline's implementation
456 DCHECK(decryptor_ready_cb_.is_null());
458 // Mixed use of prefixed and unprefixed EME APIs is disallowed by Blink.
459 DCHECK(!proxy_decryptor_ || !web_cdm_);
461 if (proxy_decryptor_) {
462 decryptor_ready_cb.Run(proxy_decryptor_->GetDecryptor(),
463 base::Bind(DoNothing));
468 decryptor_ready_cb.Run(web_cdm_->GetDecryptor(), base::Bind(DoNothing));
472 decryptor_ready_cb_ = decryptor_ready_cb;
475 } // namespace content