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