DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerator
widevine_key_source.cc
1 // Copyright 2014 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/media/base/widevine_key_source.h"
8 
9 #include <set>
10 
11 #include "packager/base/base64.h"
12 #include "packager/base/bind.h"
13 #include "packager/base/json/json_reader.h"
14 #include "packager/base/json/json_writer.h"
15 #include "packager/base/memory/ref_counted.h"
16 #include "packager/base/stl_util.h"
17 #include "packager/media/base/fixed_key_source.h"
18 #include "packager/media/base/http_key_fetcher.h"
19 #include "packager/media/base/producer_consumer_queue.h"
20 #include "packager/media/base/protection_system_specific_info.h"
21 #include "packager/media/base/request_signer.h"
22 #include "packager/media/base/widevine_pssh_data.pb.h"
23 
24 #define RCHECK(x) \
25  do { \
26  if (!(x)) { \
27  LOG(ERROR) << "Failure while processing: " << #x; \
28  return false; \
29  } \
30  } while (0)
31 
32 namespace edash_packager {
33 namespace {
34 
35 const bool kEnableKeyRotation = true;
36 
37 const char kLicenseStatusOK[] = "OK";
38 // Server may return INTERNAL_ERROR intermittently, which is a transient error
39 // and the next client request may succeed without problem.
40 const char kLicenseStatusTransientError[] = "INTERNAL_ERROR";
41 
42 // Number of times to retry requesting keys in case of a transient error from
43 // the server.
44 const int kNumTransientErrorRetries = 5;
45 const int kFirstRetryDelayMilliseconds = 1000;
46 
47 // Default crypto period count, which is the number of keys to fetch on every
48 // key rotation enabled request.
49 const int kDefaultCryptoPeriodCount = 10;
50 const int kGetKeyTimeoutInSeconds = 5 * 60; // 5 minutes.
51 const int kKeyFetchTimeoutInSeconds = 60; // 1 minute.
52 
53 const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
54  0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
55  0xd5, 0x1d, 0x21, 0xed};
56 
57 bool Base64StringToBytes(const std::string& base64_string,
58  std::vector<uint8_t>* bytes) {
59  DCHECK(bytes);
60  std::string str;
61  if (!base::Base64Decode(base64_string, &str))
62  return false;
63  bytes->assign(str.begin(), str.end());
64  return true;
65 }
66 
67 void BytesToBase64String(const std::vector<uint8_t>& bytes,
68  std::string* base64_string) {
69  DCHECK(base64_string);
70  base::Base64Encode(base::StringPiece(reinterpret_cast<const char*>
71  (bytes.data()), bytes.size()),
72  base64_string);
73 }
74 
75 bool GetKeyFromTrack(const base::DictionaryValue& track_dict,
76  std::vector<uint8_t>* key) {
77  DCHECK(key);
78  std::string key_base64_string;
79  RCHECK(track_dict.GetString("key", &key_base64_string));
80  VLOG(2) << "Key:" << key_base64_string;
81  RCHECK(Base64StringToBytes(key_base64_string, key));
82  return true;
83 }
84 
85 bool GetKeyIdFromTrack(const base::DictionaryValue& track_dict,
86  std::vector<uint8_t>* key_id) {
87  DCHECK(key_id);
88  std::string key_id_base64_string;
89  RCHECK(track_dict.GetString("key_id", &key_id_base64_string));
90  VLOG(2) << "Keyid:" << key_id_base64_string;
91  RCHECK(Base64StringToBytes(key_id_base64_string, key_id));
92  return true;
93 }
94 
95 bool GetPsshDataFromTrack(const base::DictionaryValue& track_dict,
96  std::vector<uint8_t>* pssh_data) {
97  DCHECK(pssh_data);
98 
99  const base::ListValue* pssh_list;
100  RCHECK(track_dict.GetList("pssh", &pssh_list));
101  // Invariant check. We don't want to crash in release mode if possible.
102  // The following code handles it gracefully if GetSize() does not return 1.
103  DCHECK_EQ(1u, pssh_list->GetSize());
104 
105  const base::DictionaryValue* pssh_dict;
106  RCHECK(pssh_list->GetDictionary(0, &pssh_dict));
107  std::string drm_type;
108  RCHECK(pssh_dict->GetString("drm_type", &drm_type));
109  if (drm_type != "WIDEVINE") {
110  LOG(ERROR) << "Expecting drm_type 'WIDEVINE', get '" << drm_type << "'.";
111  return false;
112  }
113  std::string pssh_data_base64_string;
114  RCHECK(pssh_dict->GetString("data", &pssh_data_base64_string));
115 
116  VLOG(2) << "Pssh Data:" << pssh_data_base64_string;
117  RCHECK(Base64StringToBytes(pssh_data_base64_string, pssh_data));
118  return true;
119 }
120 
121 } // namespace
122 
123 namespace media {
124 
125 // A ref counted wrapper for EncryptionKeyMap.
126 class WidevineKeySource::RefCountedEncryptionKeyMap
127  : public base::RefCountedThreadSafe<RefCountedEncryptionKeyMap> {
128  public:
129  explicit RefCountedEncryptionKeyMap(EncryptionKeyMap* encryption_key_map) {
130  DCHECK(encryption_key_map);
131  encryption_key_map_.swap(*encryption_key_map);
132  }
133 
134  std::map<KeySource::TrackType, EncryptionKey*>& map() {
135  return encryption_key_map_;
136  }
137 
138  private:
139  friend class base::RefCountedThreadSafe<RefCountedEncryptionKeyMap>;
140 
141  ~RefCountedEncryptionKeyMap() { STLDeleteValues(&encryption_key_map_); }
142 
143  EncryptionKeyMap encryption_key_map_;
144 
145  DISALLOW_COPY_AND_ASSIGN(RefCountedEncryptionKeyMap);
146 };
147 
148 WidevineKeySource::WidevineKeySource(const std::string& server_url,
149  bool add_common_pssh)
150  : key_production_thread_("KeyProductionThread",
151  base::Bind(&WidevineKeySource::FetchKeysTask,
152  base::Unretained(this))),
153  key_fetcher_(new HttpKeyFetcher(kKeyFetchTimeoutInSeconds)),
154  server_url_(server_url),
155  crypto_period_count_(kDefaultCryptoPeriodCount),
156  add_common_pssh_(add_common_pssh),
157  key_production_started_(false),
158  start_key_production_(false, false),
159  first_crypto_period_index_(0) {
160  key_production_thread_.Start();
161 }
162 
163 WidevineKeySource::~WidevineKeySource() {
164  if (key_pool_)
165  key_pool_->Stop();
166  if (key_production_thread_.HasBeenStarted()) {
167  // Signal the production thread to start key production if it is not
168  // signaled yet so the thread can be joined.
169  start_key_production_.Signal();
170  key_production_thread_.Join();
171  }
172  STLDeleteValues(&encryption_key_map_);
173 }
174 
175 Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& content_id,
176  const std::string& policy) {
177  base::AutoLock scoped_lock(lock_);
178  request_dict_.Clear();
179  std::string content_id_base64_string;
180  BytesToBase64String(content_id, &content_id_base64_string);
181  request_dict_.SetString("content_id", content_id_base64_string);
182  request_dict_.SetString("policy", policy);
183  return FetchKeysInternal(!kEnableKeyRotation, 0, false);
184 }
185 
186 Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& pssh_box) {
187  const std::vector<uint8_t> widevine_system_id(
188  kWidevineSystemId, kWidevineSystemId + arraysize(kWidevineSystemId));
189 
191  if (!info.Parse(pssh_box.data(), pssh_box.size()))
192  return Status(error::PARSER_FAILURE, "Error parsing the PSSH box.");
193 
194  if (info.system_id() == widevine_system_id) {
195  base::AutoLock scoped_lock(lock_);
196  request_dict_.Clear();
197  std::string pssh_data_base64_string;
198 
199  BytesToBase64String(info.pssh_data(), &pssh_data_base64_string);
200  request_dict_.SetString("pssh_data", pssh_data_base64_string);
201  return FetchKeysInternal(!kEnableKeyRotation, 0, false);
202  } else if (!info.key_ids().empty()) {
203  // This is not a Widevine PSSH box. Try making the request for the key-IDs.
204  // Even if this is a different key-system, it should still work. Either
205  // the server will not recognize it and return an error, or it will
206  // recognize it and the key must be correct (or the content is bad).
207  return FetchKeys(info.key_ids());
208  } else {
209  return Status(error::NOT_FOUND, "No key IDs given in PSSH box.");
210  }
211 }
212 
214  const std::vector<std::vector<uint8_t>>& key_ids) {
215  base::AutoLock scoped_lock(lock_);
216  request_dict_.Clear();
217  std::string pssh_data_base64_string;
218 
219  // Generate Widevine PSSH data from the key-IDs.
220  WidevinePsshData widevine_pssh_data;
221  for (size_t i = 0; i < key_ids.size(); i++) {
222  widevine_pssh_data.add_key_id(key_ids[i].data(), key_ids[i].size());
223  }
224 
225  const std::string serialized_string = widevine_pssh_data.SerializeAsString();
226  std::vector<uint8_t> pssh_data(serialized_string.begin(),
227  serialized_string.end());
228 
229  BytesToBase64String(pssh_data, &pssh_data_base64_string);
230  request_dict_.SetString("pssh_data", pssh_data_base64_string);
231  return FetchKeysInternal(!kEnableKeyRotation, 0, false);
232 }
233 
235  base::AutoLock scoped_lock(lock_);
236  request_dict_.Clear();
237  // Javascript/JSON does not support int64_t or unsigned numbers. Use double
238  // instead as 32-bit integer can be lossless represented using double.
239  request_dict_.SetDouble("asset_id", asset_id);
240  return FetchKeysInternal(!kEnableKeyRotation, 0, true);
241 }
242 
243 Status WidevineKeySource::GetKey(TrackType track_type, EncryptionKey* key) {
244  DCHECK(key);
245  if (encryption_key_map_.find(track_type) == encryption_key_map_.end()) {
246  return Status(error::INTERNAL_ERROR,
247  "Cannot find key of type " + TrackTypeToString(track_type));
248  }
249  *key = *encryption_key_map_[track_type];
250  return Status::OK;
251 }
252 
253 Status WidevineKeySource::GetKey(const std::vector<uint8_t>& key_id,
254  EncryptionKey* key) {
255  DCHECK(key);
256  for (std::map<TrackType, EncryptionKey*>::iterator iter =
257  encryption_key_map_.begin();
258  iter != encryption_key_map_.end();
259  ++iter) {
260  if (iter->second->key_id == key_id) {
261  *key = *iter->second;
262  return Status::OK;
263  }
264  }
265  return Status(error::INTERNAL_ERROR,
266  "Cannot find key with specified key ID");
267 }
268 
269 Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
270  TrackType track_type,
271  EncryptionKey* key) {
272  DCHECK(key_production_thread_.HasBeenStarted());
273  // TODO(kqyang): This is not elegant. Consider refactoring later.
274  {
275  base::AutoLock scoped_lock(lock_);
276  if (!key_production_started_) {
277  // Another client may have a slightly smaller starting crypto period
278  // index. Set the initial value to account for that.
279  first_crypto_period_index_ =
280  crypto_period_index ? crypto_period_index - 1 : 0;
281  DCHECK(!key_pool_);
282  key_pool_.reset(new EncryptionKeyQueue(crypto_period_count_,
283  first_crypto_period_index_));
284  start_key_production_.Signal();
285  key_production_started_ = true;
286  }
287  }
288  return GetKeyInternal(crypto_period_index, track_type, key);
289 }
290 
291 void WidevineKeySource::set_signer(scoped_ptr<RequestSigner> signer) {
292  signer_ = signer.Pass();
293 }
294 
295 void WidevineKeySource::set_key_fetcher(scoped_ptr<KeyFetcher> key_fetcher) {
296  key_fetcher_ = key_fetcher.Pass();
297 }
298 
299 Status WidevineKeySource::GetKeyInternal(uint32_t crypto_period_index,
300  TrackType track_type,
301  EncryptionKey* key) {
302  DCHECK(key_pool_);
303  DCHECK(key);
304  DCHECK_LE(track_type, NUM_VALID_TRACK_TYPES);
305  DCHECK_NE(track_type, TRACK_TYPE_UNKNOWN);
306 
307  scoped_refptr<RefCountedEncryptionKeyMap> ref_counted_encryption_key_map;
308  Status status =
309  key_pool_->Peek(crypto_period_index, &ref_counted_encryption_key_map,
310  kGetKeyTimeoutInSeconds * 1000);
311  if (!status.ok()) {
312  if (status.error_code() == error::STOPPED) {
313  CHECK(!common_encryption_request_status_.ok());
314  return common_encryption_request_status_;
315  }
316  return status;
317  }
318 
319  EncryptionKeyMap& encryption_key_map = ref_counted_encryption_key_map->map();
320  if (encryption_key_map.find(track_type) == encryption_key_map.end()) {
321  return Status(error::INTERNAL_ERROR,
322  "Cannot find key of type " + TrackTypeToString(track_type));
323  }
324  *key = *encryption_key_map[track_type];
325  return Status::OK;
326 }
327 
328 void WidevineKeySource::FetchKeysTask() {
329  // Wait until key production is signaled.
330  start_key_production_.Wait();
331  if (!key_pool_ || key_pool_->Stopped())
332  return;
333 
334  Status status = FetchKeysInternal(kEnableKeyRotation,
335  first_crypto_period_index_,
336  false);
337  while (status.ok()) {
338  first_crypto_period_index_ += crypto_period_count_;
339  status = FetchKeysInternal(kEnableKeyRotation,
340  first_crypto_period_index_,
341  false);
342  }
343  common_encryption_request_status_ = status;
344  key_pool_->Stop();
345 }
346 
347 Status WidevineKeySource::FetchKeysInternal(bool enable_key_rotation,
348  uint32_t first_crypto_period_index,
349  bool widevine_classic) {
350  std::string request;
351  FillRequest(enable_key_rotation,
352  first_crypto_period_index,
353  &request);
354 
355  std::string message;
356  Status status = GenerateKeyMessage(request, &message);
357  if (!status.ok())
358  return status;
359  VLOG(1) << "Message: " << message;
360 
361  std::string raw_response;
362  int64_t sleep_duration = kFirstRetryDelayMilliseconds;
363 
364  // Perform client side retries if seeing server transient error to workaround
365  // server limitation.
366  for (int i = 0; i < kNumTransientErrorRetries; ++i) {
367  status = key_fetcher_->FetchKeys(server_url_, message, &raw_response);
368  if (status.ok()) {
369  VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
370 
371  std::string response;
372  if (!DecodeResponse(raw_response, &response)) {
373  return Status(error::SERVER_ERROR,
374  "Failed to decode response '" + raw_response + "'.");
375  }
376 
377  bool transient_error = false;
378  if (ExtractEncryptionKey(enable_key_rotation,
379  widevine_classic,
380  response,
381  &transient_error))
382  return Status::OK;
383 
384  if (!transient_error) {
385  return Status(
386  error::SERVER_ERROR,
387  "Failed to extract encryption key from '" + response + "'.");
388  }
389  } else if (status.error_code() != error::TIME_OUT) {
390  return status;
391  }
392 
393  // Exponential backoff.
394  if (i != kNumTransientErrorRetries - 1) {
395  base::PlatformThread::Sleep(
396  base::TimeDelta::FromMilliseconds(sleep_duration));
397  sleep_duration *= 2;
398  }
399  }
400  return Status(error::SERVER_ERROR,
401  "Failed to recover from server internal error.");
402 }
403 
404 void WidevineKeySource::FillRequest(bool enable_key_rotation,
405  uint32_t first_crypto_period_index,
406  std::string* request) {
407  DCHECK(request);
408  DCHECK(!request_dict_.empty());
409 
410  // Build tracks.
411  base::ListValue* tracks = new base::ListValue();
412 
413  base::DictionaryValue* track_sd = new base::DictionaryValue();
414  track_sd->SetString("type", "SD");
415  tracks->Append(track_sd);
416  base::DictionaryValue* track_hd = new base::DictionaryValue();
417  track_hd->SetString("type", "HD");
418  tracks->Append(track_hd);
419  base::DictionaryValue* track_audio = new base::DictionaryValue();
420  track_audio->SetString("type", "AUDIO");
421  tracks->Append(track_audio);
422 
423  request_dict_.Set("tracks", tracks);
424 
425  // Build DRM types.
426  base::ListValue* drm_types = new base::ListValue();
427  drm_types->AppendString("WIDEVINE");
428  request_dict_.Set("drm_types", drm_types);
429 
430  // Build key rotation fields.
431  if (enable_key_rotation) {
432  // Javascript/JSON does not support int64_t or unsigned numbers. Use double
433  // instead as 32-bit integer can be lossless represented using double.
434  request_dict_.SetDouble("first_crypto_period_index",
435  first_crypto_period_index);
436  request_dict_.SetInteger("crypto_period_count", crypto_period_count_);
437  }
438 
439  base::JSONWriter::WriteWithOptions(
440  request_dict_,
441  // Write doubles that have no fractional part as a normal integer, i.e.
442  // without using exponential notation or appending a '.0'.
443  base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION, request);
444 }
445 
446 Status WidevineKeySource::GenerateKeyMessage(const std::string& request,
447  std::string* message) {
448  DCHECK(message);
449 
450  std::string request_base64_string;
451  base::Base64Encode(request, &request_base64_string);
452 
453  base::DictionaryValue request_dict;
454  request_dict.SetString("request", request_base64_string);
455 
456  // Sign the request.
457  if (signer_) {
458  std::string signature;
459  if (!signer_->GenerateSignature(request, &signature))
460  return Status(error::INTERNAL_ERROR, "Signature generation failed.");
461 
462  std::string signature_base64_string;
463  base::Base64Encode(signature, &signature_base64_string);
464 
465  request_dict.SetString("signature", signature_base64_string);
466  request_dict.SetString("signer", signer_->signer_name());
467  }
468 
469  base::JSONWriter::Write(request_dict, message);
470  return Status::OK;
471 }
472 
473 bool WidevineKeySource::DecodeResponse(
474  const std::string& raw_response,
475  std::string* response) {
476  DCHECK(response);
477 
478  // Extract base64 formatted response from JSON formatted raw response.
479  scoped_ptr<base::Value> root(base::JSONReader::Read(raw_response));
480  if (!root) {
481  LOG(ERROR) << "'" << raw_response << "' is not in JSON format.";
482  return false;
483  }
484  const base::DictionaryValue* response_dict = NULL;
485  RCHECK(root->GetAsDictionary(&response_dict));
486 
487  std::string response_base64_string;
488  RCHECK(response_dict->GetString("response", &response_base64_string));
489  RCHECK(base::Base64Decode(response_base64_string, response));
490  return true;
491 }
492 
493 bool WidevineKeySource::ExtractEncryptionKey(
494  bool enable_key_rotation,
495  bool widevine_classic,
496  const std::string& response,
497  bool* transient_error) {
498  DCHECK(transient_error);
499  *transient_error = false;
500 
501  scoped_ptr<base::Value> root(base::JSONReader::Read(response));
502  if (!root) {
503  LOG(ERROR) << "'" << response << "' is not in JSON format.";
504  return false;
505  }
506 
507  const base::DictionaryValue* license_dict = NULL;
508  RCHECK(root->GetAsDictionary(&license_dict));
509 
510  std::string license_status;
511  RCHECK(license_dict->GetString("status", &license_status));
512  if (license_status != kLicenseStatusOK) {
513  LOG(ERROR) << "Received non-OK license response: " << response;
514  *transient_error = (license_status == kLicenseStatusTransientError);
515  return false;
516  }
517 
518  const base::ListValue* tracks;
519  RCHECK(license_dict->GetList("tracks", &tracks));
520  // Should have at least one track per crypto_period.
521  RCHECK(enable_key_rotation ? tracks->GetSize() >= 1 * crypto_period_count_
522  : tracks->GetSize() >= 1);
523 
524  int current_crypto_period_index = first_crypto_period_index_;
525 
526  EncryptionKeyMap encryption_key_map;
527  for (size_t i = 0; i < tracks->GetSize(); ++i) {
528  const base::DictionaryValue* track_dict;
529  RCHECK(tracks->GetDictionary(i, &track_dict));
530 
531  if (enable_key_rotation) {
532  int crypto_period_index;
533  RCHECK(
534  track_dict->GetInteger("crypto_period_index", &crypto_period_index));
535  if (crypto_period_index != current_crypto_period_index) {
536  if (crypto_period_index != current_crypto_period_index + 1) {
537  LOG(ERROR) << "Expecting crypto period index "
538  << current_crypto_period_index << " or "
539  << current_crypto_period_index + 1 << "; Seen "
540  << crypto_period_index << " at track " << i;
541  return false;
542  }
543  if (!PushToKeyPool(&encryption_key_map))
544  return false;
545  ++current_crypto_period_index;
546  }
547  }
548 
549  std::string track_type_str;
550  RCHECK(track_dict->GetString("type", &track_type_str));
551  TrackType track_type = GetTrackTypeFromString(track_type_str);
552  DCHECK_NE(TRACK_TYPE_UNKNOWN, track_type);
553  RCHECK(encryption_key_map.find(track_type) == encryption_key_map.end());
554 
555  scoped_ptr<EncryptionKey> encryption_key(new EncryptionKey());
556 
557  if (!GetKeyFromTrack(*track_dict, &encryption_key->key))
558  return false;
559 
560  // Get key ID and PSSH data for CENC content only.
561  if (!widevine_classic) {
562  if (!GetKeyIdFromTrack(*track_dict, &encryption_key->key_id))
563  return false;
564 
565  ProtectionSystemSpecificInfo info;
566  info.add_key_id(encryption_key->key_id);
567  info.set_system_id(kWidevineSystemId, arraysize(kWidevineSystemId));
568  info.set_pssh_box_version(0);
569 
570  std::vector<uint8_t> pssh_data;
571  if (!GetPsshDataFromTrack(*track_dict, &pssh_data))
572  return false;
573  info.set_pssh_data(pssh_data);
574 
575  encryption_key->key_system_info.push_back(info);
576  }
577  encryption_key_map[track_type] = encryption_key.release();
578  }
579 
580  // If the flag exists, create a common system ID PSSH box that contains the
581  // key IDs of all the keys.
582  if (add_common_pssh_ && !widevine_classic) {
583  std::set<std::vector<uint8_t>> key_ids;
584  for (const EncryptionKeyMap::value_type& pair : encryption_key_map) {
585  key_ids.insert(pair.second->key_id);
586  }
587 
588  // Create a common system PSSH box.
589  ProtectionSystemSpecificInfo info;
590  info.set_system_id(kCommonSystemId, arraysize(kCommonSystemId));
591  info.set_pssh_box_version(1);
592  for (const std::vector<uint8_t>& key_id : key_ids) {
593  info.add_key_id(key_id);
594  }
595 
596  for (const EncryptionKeyMap::value_type& pair : encryption_key_map) {
597  pair.second->key_system_info.push_back(info);
598  }
599  }
600 
601  DCHECK(!encryption_key_map.empty());
602  if (!enable_key_rotation) {
603  encryption_key_map_ = encryption_key_map;
604  return true;
605  }
606  return PushToKeyPool(&encryption_key_map);
607 }
608 
609 bool WidevineKeySource::PushToKeyPool(
610  EncryptionKeyMap* encryption_key_map) {
611  DCHECK(key_pool_);
612  DCHECK(encryption_key_map);
613  Status status =
614  key_pool_->Push(scoped_refptr<RefCountedEncryptionKeyMap>(
615  new RefCountedEncryptionKeyMap(encryption_key_map)),
616  kInfiniteTimeout);
617  encryption_key_map->clear();
618  if (!status.ok()) {
619  DCHECK_EQ(error::STOPPED, status.error_code());
620  return false;
621  }
622  return true;
623 }
624 
625 } // namespace media
626 } // namespace edash_packager
void set_signer(scoped_ptr< RequestSigner > signer)
Status GetKey(TrackType track_type, EncryptionKey *key) override
Status FetchKeys(const std::vector< uint8_t > &pssh_box) override
void set_key_fetcher(scoped_ptr< KeyFetcher > key_fetcher)
WidevineKeySource(const std::string &server_url, bool add_common_pssh)
Status GetCryptoPeriodKey(uint32_t crypto_period_index, TrackType track_type, EncryptionKey *key) override
static TrackType GetTrackTypeFromString(const std::string &track_type_string)
Convert string representation of track type to enum representation.
Definition: key_source.cc:19
static std::string TrackTypeToString(TrackType track_type)
Convert TrackType to string.
Definition: key_source.cc:33