Shaka Packager SDK
simple_hls_notifier.cc
1 // Copyright 2016 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/hls/base/simple_hls_notifier.h"
8 
9 #include <cmath>
10 
11 #include "packager/base/base64.h"
12 #include "packager/base/files/file_path.h"
13 #include "packager/base/json/json_writer.h"
14 #include "packager/base/logging.h"
15 #include "packager/base/optional.h"
16 #include "packager/base/strings/string_number_conversions.h"
17 #include "packager/base/strings/stringprintf.h"
18 #include "packager/hls/base/media_playlist.h"
19 #include "packager/media/base/protection_system_specific_info.h"
20 #include "packager/media/base/raw_key_source.h"
21 #include "packager/media/base/widevine_key_source.h"
22 #include "packager/media/base/widevine_pssh_data.pb.h"
23 
24 namespace shaka {
25 
26 using base::FilePath;
27 
28 namespace hls {
29 
30 namespace {
31 
32 const char kUriBase64Prefix[] = "data:text/plain;base64,";
33 const char kUriFairplayPrefix[] = "skd://";
34 const char kWidevineDashIfIopUUID[] =
35  "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
36 
37 bool IsWidevineSystemId(const std::vector<uint8_t>& system_id) {
38  return system_id.size() == arraysize(media::kWidevineSystemId) &&
39  std::equal(system_id.begin(), system_id.end(),
40  media::kWidevineSystemId);
41 }
42 
43 bool IsCommonSystemId(const std::vector<uint8_t>& system_id) {
44  return system_id.size() == arraysize(media::kCommonSystemId) &&
45  std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
46 }
47 
48 bool IsFairplaySystemId(const std::vector<uint8_t>& system_id) {
49  return system_id.size() == arraysize(media::kFairplaySystemId) &&
50  std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId);
51 }
52 
53 std::string Base64EncodeData(const std::string& prefix,
54  const std::string& data) {
55  std::string data_base64;
56  base::Base64Encode(data, &data_base64);
57  return prefix + data_base64;
58 }
59 
60 std::string VectorToString(const std::vector<uint8_t>& v) {
61  return std::string(v.begin(), v.end());
62 }
63 
64 // TODO(rkuroiwa): Dedup these with the functions in MpdBuilder.
65 // If |media_path| is contained in |parent_path|, then
66 // Strips the common path and keep only the relative part of |media_path|.
67 // e.g. if |parent_path| is /some/parent/ and
68 // |media_path| is /some/parent/abc/child/item.ext,
69 // abc/child/item.ext is returned.
70 // else
71 // Returns |media_path|.
72 // The path separator of the output is also changed to "/" if it is not.
73 std::string MakePathRelative(const std::string& media_path,
74  const FilePath& parent_path) {
75  FilePath relative_path;
76  const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
77  const bool is_child =
78  parent_path.AppendRelativePath(child_path, &relative_path);
79  if (!is_child)
80  relative_path = child_path;
81  return relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe();
82 }
83 
84 // Segment URL is relative to either output directory or the directory
85 // containing the media playlist depends on whether base_url is set.
86 std::string GenerateSegmentUrl(const std::string& segment_name,
87  const std::string& base_url,
88  const std::string& output_dir,
89  const std::string& playlist_file_name) {
90  FilePath output_path = FilePath::FromUTF8Unsafe(output_dir);
91  if (!base_url.empty()) {
92  // Media segment URL is base_url + segment path relative to output
93  // directory.
94  return base_url + MakePathRelative(segment_name, output_path);
95  }
96  // Media segment URL is segment path relative to the directory containing the
97  // playlist.
98  const FilePath playlist_dir =
99  output_path.Append(FilePath::FromUTF8Unsafe(playlist_file_name))
100  .DirName()
101  .AsEndingWithSeparator();
102  return MakePathRelative(segment_name, playlist_dir);
103 }
104 
105 bool WidevinePsshToJson(const std::vector<uint8_t>& pssh_box,
106  const std::vector<uint8_t>& key_id,
107  std::string* pssh_json) {
108  media::ProtectionSystemSpecificInfo pssh_info;
109  if (!pssh_info.Parse(pssh_box.data(), pssh_box.size())) {
110  LOG(ERROR) << "Failed to parse PSSH box.";
111  return false;
112  }
113 
114  media::WidevinePsshData pssh_proto;
115  if (!pssh_proto.ParseFromArray(pssh_info.pssh_data().data(),
116  pssh_info.pssh_data().size())) {
117  LOG(ERROR) << "Failed to parse protection_system_specific_data.";
118  return false;
119  }
120  if (!pssh_proto.has_provider() ||
121  (!pssh_proto.has_content_id() && pssh_proto.key_id_size() == 0)) {
122  LOG(ERROR) << "Missing fields to generate URI.";
123  return false;
124  }
125 
126  base::DictionaryValue pssh_dict;
127  pssh_dict.SetString("provider", pssh_proto.provider());
128  if (pssh_proto.has_content_id()) {
129  std::string content_id_base64;
130  base::Base64Encode(base::StringPiece(pssh_proto.content_id().data(),
131  pssh_proto.content_id().size()),
132  &content_id_base64);
133  pssh_dict.SetString("content_id", content_id_base64);
134  }
135  base::ListValue* key_ids = new base::ListValue();
136 
137  key_ids->AppendString(base::HexEncode(key_id.data(), key_id.size()));
138  for (const std::string& id : pssh_proto.key_id()) {
139  if (key_id.size() == id.size() &&
140  memcmp(key_id.data(), id.data(), id.size()) == 0) {
141  continue;
142  }
143  key_ids->AppendString(base::HexEncode(id.data(), id.size()));
144  }
145  pssh_dict.Set("key_ids", key_ids);
146 
147  if (!base::JSONWriter::Write(pssh_dict, pssh_json)) {
148  LOG(ERROR) << "Failed to write to JSON.";
149  return false;
150  }
151  return true;
152 }
153 
154 base::Optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
155  const std::string& method) {
156  if (method == "cenc") {
157  return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
158  } else if (method == "cbcs") {
159  return MediaPlaylist::EncryptionMethod::kSampleAes;
160  } else if (method == "cbca") {
161  // cbca is a place holder for sample aes.
162  return MediaPlaylist::EncryptionMethod::kSampleAes;
163  } else {
164  return base::nullopt;
165  }
166 }
167 
168 void NotifyEncryptionToMediaPlaylist(
169  MediaPlaylist::EncryptionMethod encryption_method,
170  const std::string& uri,
171  const std::vector<uint8_t>& key_id,
172  const std::vector<uint8_t>& iv,
173  const std::string& key_format,
174  const std::string& key_format_version,
175  MediaPlaylist* media_playlist) {
176  std::string iv_string;
177  if (!iv.empty()) {
178  iv_string = "0x" + base::HexEncode(iv.data(), iv.size());
179  }
180  std::string key_id_string;
181  if (!key_id.empty()) {
182  key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size());
183  }
184 
185  media_playlist->AddEncryptionInfo(
186  encryption_method,
187  uri, key_id_string, iv_string,
188  key_format, key_format_version);
189 }
190 
191 // Creates JSON format and the format similar to MPD.
192 bool HandleWidevineKeyFormats(
193  MediaPlaylist::EncryptionMethod encryption_method,
194  const std::vector<uint8_t>& key_id,
195  const std::vector<uint8_t>& iv,
196  const std::vector<uint8_t>& protection_system_specific_data,
197  MediaPlaylist* media_playlist) {
198  if (encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) {
199  // This format allows SAMPLE-AES only.
200  std::string key_uri_data;
201  if (!WidevinePsshToJson(protection_system_specific_data, key_id,
202  &key_uri_data)) {
203  return false;
204  }
205  std::string key_uri_data_base64 =
206  Base64EncodeData(kUriBase64Prefix, key_uri_data);
207  NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
208  std::vector<uint8_t>(), iv, "com.widevine",
209  "1", media_playlist);
210  }
211 
212  std::string pssh_as_string(
213  reinterpret_cast<const char*>(protection_system_specific_data.data()),
214  protection_system_specific_data.size());
215  std::string key_uri_data_base64 =
216  Base64EncodeData(kUriBase64Prefix, pssh_as_string);
217  NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
218  key_id, iv, kWidevineDashIfIopUUID, "1",
219  media_playlist);
220  return true;
221 }
222 
223 bool WriteMediaPlaylist(const std::string& output_dir,
224  MediaPlaylist* playlist) {
225  std::string file_path =
226  FilePath::FromUTF8Unsafe(output_dir)
227  .Append(FilePath::FromUTF8Unsafe(playlist->file_name()))
228  .AsUTF8Unsafe();
229  if (!playlist->WriteToFile(file_path)) {
230  LOG(ERROR) << "Failed to write playlist " << file_path;
231  return false;
232  }
233  return true;
234 }
235 
236 } // namespace
237 
238 MediaPlaylistFactory::~MediaPlaylistFactory() {}
239 
240 std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
241  HlsPlaylistType type,
242  double time_shift_buffer_depth,
243  const std::string& file_name,
244  const std::string& name,
245  const std::string& group_id) {
246  return std::unique_ptr<MediaPlaylist>(new MediaPlaylist(
247  type, time_shift_buffer_depth, file_name, name, group_id));
248 }
249 
251  : HlsNotifier(hls_params.playlist_type),
252  time_shift_buffer_depth_(hls_params.time_shift_buffer_depth),
253  prefix_(hls_params.base_url),
254  key_uri_(hls_params.key_uri),
255  media_playlist_factory_(new MediaPlaylistFactory()) {
256  const base::FilePath master_playlist_path(
257  base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
258  output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
259  master_playlist_.reset(
260  new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
261  hls_params.default_language));
262 }
263 
264 SimpleHlsNotifier::~SimpleHlsNotifier() {}
265 
267  return true;
268 }
269 
270 bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
271  const std::string& playlist_name,
272  const std::string& name,
273  const std::string& group_id,
274  uint32_t* stream_id) {
275  DCHECK(stream_id);
276 
277  std::unique_ptr<MediaPlaylist> media_playlist =
278  media_playlist_factory_->Create(playlist_type(), time_shift_buffer_depth_,
279  playlist_name, name, group_id);
280 
281  // Update init_segment_name to be relative to playlist path if needed.
282  MediaInfo media_info_copy = media_info;
283  if (media_info_copy.has_init_segment_name()) {
284  media_info_copy.set_init_segment_name(
285  GenerateSegmentUrl(media_info_copy.init_segment_name(), prefix_,
286  output_dir_, media_playlist->file_name()));
287  }
288  if (media_info_copy.has_media_file_name()) {
289  media_info_copy.set_media_file_name(
290  GenerateSegmentUrl(media_info_copy.media_file_name(), prefix_,
291  output_dir_, media_playlist->file_name()));
292  }
293  if (!media_playlist->SetMediaInfo(media_info_copy)) {
294  LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
295  return false;
296  }
297 
298  MediaPlaylist::EncryptionMethod encryption_method =
299  MediaPlaylist::EncryptionMethod::kNone;
300  if (media_info.protected_content().has_protection_scheme()) {
301  const std::string& protection_scheme =
302  media_info.protected_content().protection_scheme();
303  base::Optional<MediaPlaylist::EncryptionMethod> enc_method =
304  StringToEncryptionMethod(protection_scheme);
305  if (!enc_method) {
306  LOG(ERROR) << "Failed to recognize protection scheme "
307  << protection_scheme;
308  return false;
309  }
310  encryption_method = enc_method.value();
311  }
312 
313  *stream_id = sequence_number_.GetNext();
314  base::AutoLock auto_lock(lock_);
315  media_playlists_.push_back(media_playlist.get());
316  stream_map_[*stream_id].reset(
317  new StreamEntry{std::move(media_playlist), encryption_method});
318  return true;
319 }
320 
321 bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id,
322  const std::string& segment_name,
323  uint64_t start_time,
324  uint64_t duration,
325  uint64_t start_byte_offset,
326  uint64_t size) {
327  base::AutoLock auto_lock(lock_);
328  auto stream_iterator = stream_map_.find(stream_id);
329  if (stream_iterator == stream_map_.end()) {
330  LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
331  return false;
332  }
333  auto& media_playlist = stream_iterator->second->media_playlist;
334  const std::string& segment_url = GenerateSegmentUrl(
335  segment_name, prefix_, output_dir_, media_playlist->file_name());
336  media_playlist->AddSegment(segment_url, start_time, duration,
337  start_byte_offset, size);
338 
339  // Update target duration.
340  uint32_t longest_segment_duration =
341  static_cast<uint32_t>(ceil(media_playlist->GetLongestSegmentDuration()));
342  bool target_duration_updated = false;
343  if (longest_segment_duration > target_duration_) {
344  target_duration_ = longest_segment_duration;
345  target_duration_updated = true;
346  }
347 
348  // Update the playlists when there is new segments in live mode.
349  if (playlist_type() == HlsPlaylistType::kLive ||
350  playlist_type() == HlsPlaylistType::kEvent) {
351  // Update all playlists if target duration is updated.
352  if (target_duration_updated) {
353  for (MediaPlaylist* playlist : media_playlists_) {
354  playlist->SetTargetDuration(target_duration_);
355  if (!WriteMediaPlaylist(output_dir_, playlist))
356  return false;
357  }
358  } else {
359  if (!WriteMediaPlaylist(output_dir_, media_playlist.get()))
360  return false;
361  }
362  if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
363  media_playlists_)) {
364  LOG(ERROR) << "Failed to write master playlist.";
365  return false;
366  }
367  }
368  return true;
369 }
370 
371 bool SimpleHlsNotifier::NotifyKeyFrame(uint32_t stream_id,
372  uint64_t timestamp,
373  uint64_t start_byte_offset,
374  uint64_t size) {
375  base::AutoLock auto_lock(lock_);
376  auto stream_iterator = stream_map_.find(stream_id);
377  if (stream_iterator == stream_map_.end()) {
378  LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
379  return false;
380  }
381  auto& media_playlist = stream_iterator->second->media_playlist;
382  media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
383  return true;
384 }
385 
386 bool SimpleHlsNotifier::NotifyCueEvent(uint32_t stream_id, uint64_t timestamp) {
387  base::AutoLock auto_lock(lock_);
388  auto stream_iterator = stream_map_.find(stream_id);
389  if (stream_iterator == stream_map_.end()) {
390  LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
391  return false;
392  }
393  auto& media_playlist = stream_iterator->second->media_playlist;
394  media_playlist->AddPlacementOpportunity();
395  return true;
396 }
397 
399  uint32_t stream_id,
400  const std::vector<uint8_t>& key_id,
401  const std::vector<uint8_t>& system_id,
402  const std::vector<uint8_t>& iv,
403  const std::vector<uint8_t>& protection_system_specific_data) {
404  base::AutoLock auto_lock(lock_);
405  auto stream_iterator = stream_map_.find(stream_id);
406  if (stream_iterator == stream_map_.end()) {
407  LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
408  return false;
409  }
410 
411  std::unique_ptr<MediaPlaylist>& media_playlist =
412  stream_iterator->second->media_playlist;
413  const MediaPlaylist::EncryptionMethod encryption_method =
414  stream_iterator->second->encryption_method;
415  LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
416  << "Got encryption notification but the encryption method is NONE";
417  if (IsWidevineSystemId(system_id)) {
418  return HandleWidevineKeyFormats(encryption_method,
419  key_id, iv, protection_system_specific_data,
420  media_playlist.get());
421  }
422 
423  // Key Id does not need to be specified with "identity" and "sdk".
424  const std::vector<uint8_t> empty_key_id;
425 
426  if (IsCommonSystemId(system_id)) {
427  std::string key_uri;
428  if (!key_uri_.empty()) {
429  key_uri = key_uri_;
430  } else {
431  // Use key_id as the key_uri. The player needs to have custom logic to
432  // convert it to the actual key uri.
433  std::string key_uri_data = VectorToString(key_id);
434  key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
435  }
436  NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
437  iv, "identity", "", media_playlist.get());
438  return true;
439  } else if (IsFairplaySystemId(system_id)) {
440  std::string key_uri;
441  if (!key_uri_.empty()) {
442  key_uri = key_uri_;
443  } else {
444  // Use key_id as the key_uri. The player needs to have custom logic to
445  // convert it to the actual key uri.
446  std::string key_uri_data = VectorToString(key_id);
447  key_uri = Base64EncodeData(kUriFairplayPrefix, key_uri_data);
448  }
449 
450  // Fairplay defines IV to be carried with the key, not the playlist.
451  const std::vector<uint8_t> empty_iv;
452  NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
453  empty_iv, "com.apple.streamingkeydelivery",
454  "1", media_playlist.get());
455  return true;
456  }
457 
458  LOG(ERROR) << "Unknown system ID: "
459  << base::HexEncode(system_id.data(), system_id.size());
460  return false;
461 }
462 
464  base::AutoLock auto_lock(lock_);
465  for (MediaPlaylist* playlist : media_playlists_) {
466  playlist->SetTargetDuration(target_duration_);
467  if (!WriteMediaPlaylist(output_dir_, playlist))
468  return false;
469  }
470  if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
471  media_playlists_)) {
472  LOG(ERROR) << "Failed to write master playlist.";
473  return false;
474  }
475  return true;
476 }
477 
478 } // namespace hls
479 } // namespace shaka
std::string master_playlist_output
HLS master playlist output path.
Definition: hls_params.h:27
HlsPlaylistType
Definition: hls_params.h:16
bool NotifyNewSegment(uint32_t stream_id, const std::string &segment_name, uint64_t start_time, uint64_t duration, uint64_t start_byte_offset, uint64_t size) override
HLS related parameters.
Definition: hls_params.h:23
SimpleHlsNotifier(const HlsParams &hls_params)
All the methods that are virtual are virtual for mocking.
bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override
Methods are virtual for mocking.
bool NotifyNewStream(const MediaInfo &media_info, const std::string &playlist_name, const std::string &stream_name, const std::string &group_id, uint32_t *stream_id) override
HlsPlaylistType playlist_type() const
Definition: hls_notifier.h:96
std::string default_language
Definition: hls_params.h:41
bool NotifyKeyFrame(uint32_t stream_id, uint64_t timestamp, uint64_t start_byte_offset, uint64_t size) override
bool NotifyEncryptionUpdate(uint32_t stream_id, const std::vector< uint8_t > &key_id, const std::vector< uint8_t > &system_id, const std::vector< uint8_t > &iv, const std::vector< uint8_t > &protection_system_specific_data) override