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 31using blink::WebMediaPlayer; 32using blink::WebMediaPlayerClient; 33using blink::WebString; 34 35namespace 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. 45static const char* kMediaEme = "Media.EME."; 46 47// Used for calls to decryptor_ready_cb where the result can be ignored. 48static 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. 53static 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. 63static 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 73static 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. 83enum MediaKeyException { 84 kUnknownResultId, 85 kSuccess, 86 kKeySystemNotSupported, 87 kInvalidPlayerState, 88 kMaxMediaKeyException 89}; 90 91static 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. 107static 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. 118static 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 127scoped_ptr<media::EncryptedMediaPlayerSupport> 128EncryptedMediaPlayerSupportImpl::Create(blink::WebMediaPlayerClient* client) { 129 return scoped_ptr<EncryptedMediaPlayerSupport>( 130 new EncryptedMediaPlayerSupportImpl(client)); 131} 132 133EncryptedMediaPlayerSupportImpl::EncryptedMediaPlayerSupportImpl( 134 blink::WebMediaPlayerClient* client) 135 : client_(client), 136 web_cdm_(NULL) { 137} 138 139EncryptedMediaPlayerSupportImpl::~EncryptedMediaPlayerSupportImpl() { 140} 141 142WebMediaPlayer::MediaKeyException 143EncryptedMediaPlayerSupportImpl::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 163WebMediaPlayer::MediaKeyException 164EncryptedMediaPlayerSupportImpl::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 218WebMediaPlayer::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 246WebMediaPlayer::MediaKeyException 247EncryptedMediaPlayerSupportImpl::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 268WebMediaPlayer::MediaKeyException 269EncryptedMediaPlayerSupportImpl::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 285WebMediaPlayer::MediaKeyException 286EncryptedMediaPlayerSupportImpl::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 299void 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 307void 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 320void 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 346void 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 360media::SetDecryptorReadyCB 361EncryptedMediaPlayerSupportImpl::CreateSetDecryptorReadyCB() { 362 return BIND_TO_RENDER_LOOP( 363 &EncryptedMediaPlayerSupportImpl::SetDecryptorReadyCB); 364} 365 366media::Demuxer::NeedKeyCB 367EncryptedMediaPlayerSupportImpl::CreateNeedKeyCB() { 368 return BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupportImpl::OnNeedKey); 369} 370 371void EncryptedMediaPlayerSupportImpl::OnPipelineDecryptError() { 372 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1); 373} 374 375void 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 394void 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 402void 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 423void 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 437void 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