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