Shaka Packager SDK
media_playlist.cc
1 // Copyright 2016 Google LLC. 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/media_playlist.h>
8 
9 #include <algorithm>
10 #include <cinttypes>
11 #include <cmath>
12 #include <memory>
13 #include <optional>
14 
15 #include <absl/log/check.h>
16 #include <absl/log/log.h>
17 #include <absl/strings/numbers.h>
18 #include <absl/strings/str_format.h>
19 
20 #include <packager/file.h>
21 #include <packager/hls/base/tag.h>
22 #include <packager/macros/logging.h>
23 #include <packager/media/base/language_utils.h>
24 #include <packager/media/base/muxer_util.h>
25 #include <packager/version/version.h>
26 
27 namespace shaka {
28 namespace hls {
29 
30 namespace {
31 int32_t GetTimeScale(const MediaInfo& media_info) {
32  if (media_info.has_reference_time_scale())
33  return media_info.reference_time_scale();
34 
35  if (media_info.has_video_info())
36  return media_info.video_info().time_scale();
37 
38  if (media_info.has_audio_info())
39  return media_info.audio_info().time_scale();
40  return 0;
41 }
42 
43 std::string AdjustVideoCodec(const std::string& codec) {
44  // Apple does not like video formats with the parameter sets stored in the
45  // samples. It also fails mediastreamvalidator checks and some Apple devices /
46  // platforms refused to play.
47  // See https://apple.co/30n90DC 1.10 and
48  // https://github.com/shaka-project/shaka-packager/issues/587#issuecomment-489182182.
49  // Replaced with the corresponding formats with the parameter sets stored in
50  // the sample descriptions instead.
51  std::string adjusted_codec = codec;
52  std::string fourcc = codec.substr(0, 4);
53  if (fourcc == "avc3")
54  adjusted_codec = "avc1" + codec.substr(4);
55  else if (fourcc == "hev1")
56  adjusted_codec = "hvc1" + codec.substr(4);
57  else if (fourcc == "dvhe")
58  adjusted_codec = "dvh1" + codec.substr(4);
59  if (adjusted_codec != codec) {
60  VLOG(1) << "Adusting video codec string from " << codec << " to "
61  << adjusted_codec;
62  }
63  return adjusted_codec;
64 }
65 
66 // Duplicated from MpdUtils because:
67 // 1. MpdUtils header depends on libxml header, which is not in the deps here
68 // 2. GetLanguage depends on MediaInfo from packager/mpd/
69 // 3. Moving GetLanguage to LanguageUtils would create a a media => mpd dep.
70 // TODO(https://github.com/shaka-project/shaka-packager/issues/373): Fix this
71 // dependency situation and factor this out to a common location.
72 std::string GetLanguage(const MediaInfo& media_info) {
73  std::string lang;
74  if (media_info.has_audio_info()) {
75  lang = media_info.audio_info().language();
76  } else if (media_info.has_text_info()) {
77  lang = media_info.text_info().language();
78  }
79  return LanguageToShortestForm(lang);
80 }
81 
82 void AppendExtXMap(const MediaInfo& media_info, std::string* out) {
83  if (media_info.has_init_segment_url()) {
84  Tag tag("#EXT-X-MAP", out);
85  tag.AddQuotedString("URI", media_info.init_segment_url().data());
86  out->append("\n");
87  } else if (media_info.has_media_file_url() && media_info.has_init_range()) {
88  // It only makes sense for single segment media to have EXT-X-MAP if
89  // there is init_range.
90  Tag tag("#EXT-X-MAP", out);
91  tag.AddQuotedString("URI", media_info.media_file_url().data());
92 
93  if (media_info.has_init_range()) {
94  const uint64_t begin = media_info.init_range().begin();
95  const uint64_t end = media_info.init_range().end();
96  const uint64_t length = end - begin + 1;
97 
98  tag.AddQuotedNumberPair("BYTERANGE", length, '@', begin);
99  }
100 
101  out->append("\n");
102  } else {
103  // This media info does not need an ext-x-map tag.
104  }
105 }
106 
107 std::string CreatePlaylistHeader(
108  const MediaInfo& media_info,
109  int32_t target_duration,
110  HlsPlaylistType type,
111  MediaPlaylist::MediaPlaylistStreamType stream_type,
112  uint32_t media_sequence_number,
113  int discontinuity_sequence_number,
114  std::optional<double> start_time_offset) {
115  const std::string version = GetPackagerVersion();
116  std::string version_line;
117  if (!version.empty()) {
118  version_line =
119  absl::StrFormat("## Generated with %s version %s\n",
120  GetPackagerProjectUrl().c_str(), version.c_str());
121  }
122 
123  // 6 is required for EXT-X-MAP without EXT-X-I-FRAMES-ONLY.
124  std::string header = absl::StrFormat(
125  "#EXTM3U\n"
126  "#EXT-X-VERSION:6\n"
127  "%s"
128  "#EXT-X-TARGETDURATION:%d\n",
129  version_line.c_str(), target_duration);
130 
131  switch (type) {
132  case HlsPlaylistType::kVod:
133  header += "#EXT-X-PLAYLIST-TYPE:VOD\n";
134  break;
135  case HlsPlaylistType::kEvent:
136  header += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
137  break;
138  case HlsPlaylistType::kLive:
139  if (media_sequence_number > 0) {
140  absl::StrAppendFormat(&header, "#EXT-X-MEDIA-SEQUENCE:%d\n",
141  media_sequence_number);
142  }
143  if (discontinuity_sequence_number > 0) {
144  absl::StrAppendFormat(&header, "#EXT-X-DISCONTINUITY-SEQUENCE:%d\n",
145  discontinuity_sequence_number);
146  }
147  break;
148  default:
149  NOTIMPLEMENTED() << "Unexpected MediaPlaylistType "
150  << static_cast<int>(type);
151  }
152  if (stream_type ==
153  MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
154  absl::StrAppendFormat(&header, "#EXT-X-I-FRAMES-ONLY\n");
155  }
156  if (start_time_offset.has_value()) {
157  absl::StrAppendFormat(&header, "#EXT-X-START:TIME-OFFSET=%f\n",
158  start_time_offset.value());
159  }
160 
161  // Put EXT-X-MAP at the end since the rest of the playlist is about the
162  // segment and key info.
163  AppendExtXMap(media_info, &header);
164 
165  return header;
166 }
167 
168 
169 } // namespace
170 
171 HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
172 HlsEntry::~HlsEntry() {}
173 
174 class SegmentInfoEntry : public HlsEntry {
175  public:
176  // If |use_byte_range| true then this will append EXT-X-BYTERANGE
177  // after EXTINF.
178  // It uses |previous_segment_end_offset| to determine if it has to also
179  // specify the start byte offset in the tag.
180  // |start_time| is in timescale.
181  // |duration_seconds| is duration in seconds.
182  SegmentInfoEntry(const std::string& file_name,
183  int64_t start_time,
184  double duration_seconds,
185  bool use_byte_range,
186  uint64_t start_byte_offset,
187  uint64_t segment_file_size,
188  uint64_t previous_segment_end_offset);
189 
190  std::string ToString() override;
191  int64_t start_time() const { return start_time_; }
192  double duration_seconds() const { return duration_seconds_; }
193  void set_duration_seconds(double duration_seconds) {
194  duration_seconds_ = duration_seconds;
195  }
196 
197  private:
198  SegmentInfoEntry(const SegmentInfoEntry&) = delete;
199  SegmentInfoEntry& operator=(const SegmentInfoEntry&) = delete;
200 
201  const std::string file_name_;
202  const int64_t start_time_;
203  double duration_seconds_;
204  const bool use_byte_range_;
205  const uint64_t start_byte_offset_;
206  const uint64_t segment_file_size_;
207  const uint64_t previous_segment_end_offset_;
208 };
209 
210 SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
211  int64_t start_time,
212  double duration_seconds,
213  bool use_byte_range,
214  uint64_t start_byte_offset,
215  uint64_t segment_file_size,
216  uint64_t previous_segment_end_offset)
217  : HlsEntry(HlsEntry::EntryType::kExtInf),
218  file_name_(file_name),
219  start_time_(start_time),
220  duration_seconds_(duration_seconds),
221  use_byte_range_(use_byte_range),
222  start_byte_offset_(start_byte_offset),
223  segment_file_size_(segment_file_size),
224  previous_segment_end_offset_(previous_segment_end_offset) {}
225 
226 std::string SegmentInfoEntry::ToString() {
227  std::string result = absl::StrFormat("#EXTINF:%.3f,", duration_seconds_);
228 
229  if (use_byte_range_) {
230  absl::StrAppendFormat(&result, "\n#EXT-X-BYTERANGE:%" PRIu64,
231  segment_file_size_);
232  if (previous_segment_end_offset_ + 1 != start_byte_offset_) {
233  absl::StrAppendFormat(&result, "@%" PRIu64, start_byte_offset_);
234  }
235  }
236 
237  absl::StrAppendFormat(&result, "\n%s", file_name_.c_str());
238 
239  return result;
240 }
241 
242 
243 class DiscontinuityEntry : public HlsEntry {
244  public:
245  DiscontinuityEntry();
246 
247  std::string ToString() override;
248 
249  private:
250  DiscontinuityEntry(const DiscontinuityEntry&) = delete;
251  DiscontinuityEntry& operator=(const DiscontinuityEntry&) = delete;
252 };
253 
254 DiscontinuityEntry::DiscontinuityEntry()
255  : HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {}
256 
257 std::string DiscontinuityEntry::ToString() {
258  return "#EXT-X-DISCONTINUITY";
259 }
260 
261 class PlacementOpportunityEntry : public HlsEntry {
262  public:
263  PlacementOpportunityEntry();
264 
265  std::string ToString() override;
266 
267  private:
268  PlacementOpportunityEntry(const PlacementOpportunityEntry&) = delete;
269  PlacementOpportunityEntry& operator=(const PlacementOpportunityEntry&) =
270  delete;
271 };
272 
273 PlacementOpportunityEntry::PlacementOpportunityEntry()
274  : HlsEntry(HlsEntry::EntryType::kExtPlacementOpportunity) {}
275 
276 std::string PlacementOpportunityEntry::ToString() {
277  return "#EXT-X-PLACEMENT-OPPORTUNITY";
278 }
279 
280 EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method,
281  const std::string& url,
282  const std::string& key_id,
283  const std::string& iv,
284  const std::string& key_format,
285  const std::string& key_format_versions)
286  : HlsEntry(HlsEntry::EntryType::kExtKey),
287  method_(method),
288  url_(url),
289  key_id_(key_id),
290  iv_(iv),
291  key_format_(key_format),
292  key_format_versions_(key_format_versions) {}
293 
294 std::string EncryptionInfoEntry::ToString() {
295  return ToString("");
296 }
297 
298 std::string EncryptionInfoEntry::ToString(std::string tag_name) {
299  std::string tag_string;
300  if (tag_name.empty())
301  tag_name = "#EXT-X-KEY";
302  Tag tag(tag_name, &tag_string);
303 
304  if (method_ == MediaPlaylist::EncryptionMethod::kSampleAes) {
305  tag.AddString("METHOD", "SAMPLE-AES");
306  } else if (method_ == MediaPlaylist::EncryptionMethod::kAes128) {
307  tag.AddString("METHOD", "AES-128");
308  } else if (method_ == MediaPlaylist::EncryptionMethod::kSampleAesCenc) {
309  tag.AddString("METHOD", "SAMPLE-AES-CTR");
310  } else {
311  DCHECK(method_ == MediaPlaylist::EncryptionMethod::kNone);
312  tag.AddString("METHOD", "NONE");
313  }
314 
315  tag.AddQuotedString("URI", url_);
316 
317  if (!key_id_.empty()) {
318  tag.AddString("KEYID", key_id_);
319  }
320  if (!iv_.empty()) {
321  tag.AddString("IV", iv_);
322  }
323  if (!key_format_versions_.empty()) {
324  tag.AddQuotedString("KEYFORMATVERSIONS", key_format_versions_);
325  }
326  if (!key_format_.empty()) {
327  tag.AddQuotedString("KEYFORMAT", key_format_);
328  }
329 
330  return tag_string;
331 }
332 
333 MediaPlaylist::MediaPlaylist(const HlsParams& hls_params,
334  const std::string& file_name,
335  const std::string& name,
336  const std::string& group_id)
337  : hls_params_(hls_params),
338  file_name_(file_name),
339  name_(name),
340  group_id_(group_id),
341  media_sequence_number_(hls_params_.media_sequence_number) {
342  // When there's a forced media_sequence_number, start with discontinuity
343  if (media_sequence_number_ > 0)
344  entries_.emplace_back(new DiscontinuityEntry());
345  }
346 
347 MediaPlaylist::~MediaPlaylist() {}
348 
350  MediaPlaylistStreamType stream_type) {
351  stream_type_ = stream_type;
352 }
353 
354 void MediaPlaylist::SetCodecForTesting(const std::string& codec) {
355  codec_ = codec;
356 }
357 
358 void MediaPlaylist::SetLanguageForTesting(const std::string& language) {
359  language_ = language;
360 }
361 
363  const std::vector<std::string>& characteristics) {
364  characteristics_ = characteristics;
365 }
366 
367 void MediaPlaylist::SetForcedSubtitleForTesting(const bool forced_subtitle) {
368  forced_subtitle_ = forced_subtitle;
369 }
370 
372  MediaPlaylist::EncryptionMethod method,
373  const std::string& url,
374  const std::string& key_id,
375  const std::string& iv,
376  const std::string& key_format,
377  const std::string& key_format_versions) {
378  entries_.emplace_back(new EncryptionInfoEntry(
379  method, url, key_id, iv, key_format, key_format_versions));
380 }
381 
382 bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
383  const int32_t time_scale = GetTimeScale(media_info);
384  if (time_scale == 0) {
385  LOG(ERROR) << "MediaInfo does not contain a valid timescale.";
386  return false;
387  }
388 
389  if (media_info.has_video_info()) {
390  stream_type_ = MediaPlaylistStreamType::kVideo;
391  codec_ = AdjustVideoCodec(media_info.video_info().codec());
392  if (media_info.video_info().has_supplemental_codec() &&
393  media_info.video_info().has_compatible_brand()) {
394  supplemental_codec_ =
395  AdjustVideoCodec(media_info.video_info().supplemental_codec());
396  compatible_brand_ = static_cast<media::FourCC>(
397  media_info.video_info().compatible_brand());
398  }
399  } else if (media_info.has_audio_info()) {
400  stream_type_ = MediaPlaylistStreamType::kAudio;
401  codec_ = media_info.audio_info().codec();
402  } else {
403  stream_type_ = MediaPlaylistStreamType::kSubtitle;
404  codec_ = media_info.text_info().codec();
405  }
406 
407  time_scale_ = time_scale;
408  media_info_ = media_info;
409  language_ = GetLanguage(media_info);
410  use_byte_range_ = !media_info_.has_segment_template_url() &&
411  media_info_.container_type() != MediaInfo::CONTAINER_TEXT;
412  characteristics_ =
413  std::vector<std::string>(media_info_.hls_characteristics().begin(),
414  media_info_.hls_characteristics().end());
415 
416  forced_subtitle_ = media_info_.forced_subtitle();
417 
418  return true;
419 }
420 
421 void MediaPlaylist::SetSampleDuration(int32_t sample_duration) {
422  if (media_info_.has_video_info())
423  media_info_.mutable_video_info()->set_frame_duration(sample_duration);
424 }
425 
426 void MediaPlaylist::AddSegment(const std::string& file_name,
427  int64_t start_time,
428  int64_t duration,
429  uint64_t start_byte_offset,
430  uint64_t size) {
431  if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) {
432  if (key_frames_.empty())
433  return;
434 
435  AdjustLastSegmentInfoEntryDuration(key_frames_.front().timestamp);
436 
437  for (auto iter = key_frames_.begin(); iter != key_frames_.end(); ++iter) {
438  // Last entry duration may be adjusted later when the next iframe becomes
439  // available.
440  const int64_t next_timestamp = std::next(iter) == key_frames_.end()
441  ? (start_time + duration)
442  : std::next(iter)->timestamp;
443  AddSegmentInfoEntry(file_name, iter->timestamp,
444  next_timestamp - iter->timestamp,
445  iter->start_byte_offset, iter->size);
446  }
447  key_frames_.clear();
448  return;
449  }
450  return AddSegmentInfoEntry(file_name, start_time, duration, start_byte_offset,
451  size);
452 }
453 
454 void MediaPlaylist::AddKeyFrame(int64_t timestamp,
455  uint64_t start_byte_offset,
456  uint64_t size) {
457  if (stream_type_ != MediaPlaylistStreamType::kVideoIFramesOnly) {
458  if (stream_type_ != MediaPlaylistStreamType::kVideo) {
459  LOG(WARNING)
460  << "I-Frames Only playlist applies to video renditions only.";
461  return;
462  }
463  stream_type_ = MediaPlaylistStreamType::kVideoIFramesOnly;
464  use_byte_range_ = true;
465  }
466  key_frames_.push_back({timestamp, start_byte_offset, size, std::string("")});
467 }
468 
469 void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method,
470  const std::string& url,
471  const std::string& key_id,
472  const std::string& iv,
473  const std::string& key_format,
474  const std::string& key_format_versions) {
475  if (!inserted_discontinuity_tag_) {
476  // Insert discontinuity tag only for the first EXT-X-KEY, only if there
477  // are non-encrypted media segments.
478  if (!entries_.empty())
479  entries_.emplace_back(new DiscontinuityEntry());
480  inserted_discontinuity_tag_ = true;
481  }
482  entries_.emplace_back(new EncryptionInfoEntry(
483  method, url, key_id, iv, key_format, key_format_versions));
484 }
485 
487  entries_.emplace_back(new PlacementOpportunityEntry());
488 }
489 
490 bool MediaPlaylist::WriteToFile(const std::filesystem::path& file_path) {
491  if (!target_duration_set_) {
493  }
494 
495  std::string content = CreatePlaylistHeader(
496  media_info_, target_duration_, hls_params_.playlist_type, stream_type_,
497  media_sequence_number_, discontinuity_sequence_number_,
498  hls_params_.start_time_offset);
499 
500  for (const auto& entry : entries_)
501  absl::StrAppendFormat(&content, "%s\n", entry->ToString().c_str());
502 
503  if (hls_params_.playlist_type == HlsPlaylistType::kVod) {
504  content += "#EXT-X-ENDLIST\n";
505  }
506 
507  if (!File::WriteFileAtomically(file_path.string().c_str(), content)) {
508  LOG(ERROR) << "Failed to write playlist to: " << file_path.string();
509  return false;
510  }
511  return true;
512 }
513 
514 uint64_t MediaPlaylist::MaxBitrate() const {
515  if (media_info_.has_bandwidth())
516  return media_info_.bandwidth();
517  return bandwidth_estimator_.Max();
518 }
519 
520 uint64_t MediaPlaylist::AvgBitrate() const {
521  return bandwidth_estimator_.Estimate();
522 }
523 
525  return longest_segment_duration_seconds_;
526 }
527 
528 void MediaPlaylist::SetTargetDuration(int32_t target_duration) {
529  if (target_duration_set_) {
530  if (target_duration_ == target_duration)
531  return;
532  VLOG(1) << "Updating target duration from " << target_duration_ << " to "
533  << target_duration;
534  }
535  target_duration_ = target_duration;
536  target_duration_set_ = true;
537 }
538 
540  return media_info_.audio_info().num_channels();
541 }
542 
544  return media_info_.audio_info().codec_specific_data().ec3_joc_complexity();
545 }
546 
548  return media_info_.audio_info().codec_specific_data().ac4_ims_flag();
549 }
550 
552  return media_info_.audio_info().codec_specific_data().ac4_cbi_flag();
553 }
554 
556  uint32_t* height) const {
557  DCHECK(width);
558  DCHECK(height);
559  if (media_info_.has_video_info()) {
560  const double pixel_aspect_ratio =
561  media_info_.video_info().pixel_height() > 0
562  ? static_cast<double>(media_info_.video_info().pixel_width()) /
563  media_info_.video_info().pixel_height()
564  : 1.0;
565  *width = static_cast<uint32_t>(media_info_.video_info().width() *
566  pixel_aspect_ratio);
567  *height = media_info_.video_info().height();
568  return true;
569  }
570  return false;
571 }
572 
573 std::string MediaPlaylist::GetVideoRange() const {
574  // Dolby Vision (dvh1 or dvhe) is always HDR.
575  if (codec_.find("dvh") == 0)
576  return "PQ";
577 
578  // HLS specification:
579  // https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-02#section-4.4.4.2
580  switch (media_info_.video_info().transfer_characteristics()) {
581  case 1:
582  case 6:
583  case 13:
584  case 14:
585  // Dolby Vision profile 8.4 may have a transfer_characteristics 14, the
586  // actual value refers to preferred_transfer_characteristic value in SEI
587  // message, using compatible brand as a workaround
588  if (!supplemental_codec_.empty() &&
589  compatible_brand_ == media::FOURCC_db4g)
590  return "HLG";
591  else
592  return "SDR";
593  case 15:
594  return "SDR";
595  case 16:
596  return "PQ";
597  case 18:
598  return "HLG";
599  default:
600  // Leave it empty if we do not have the transfer characteristics
601  // information.
602  return "";
603  }
604 }
605 
607  if (media_info_.video_info().frame_duration() == 0)
608  return 0;
609  return static_cast<double>(time_scale_) /
610  media_info_.video_info().frame_duration();
611 }
612 
613 void MediaPlaylist::AddSegmentInfoEntry(const std::string& segment_file_name,
614  int64_t start_time,
615  int64_t duration,
616  uint64_t start_byte_offset,
617  uint64_t size) {
618  if (time_scale_ == 0) {
619  LOG(WARNING) << "Timescale is not set and the duration for " << duration
620  << " cannot be calculated. The output will be wrong.";
621 
622  entries_.emplace_back(new SegmentInfoEntry(
623  segment_file_name, 0.0, 0.0, use_byte_range_, start_byte_offset, size,
624  previous_segment_end_offset_));
625  return;
626  }
627 
628  // In order for the oldest segment to be accessible for at least
629  // |time_shift_buffer_depth| seconds, the latest segment should not be in the
630  // sliding window since the player could be playing any part of the latest
631  // segment. So the current segment duration is added to the sum of segment
632  // durations (in the manifest/playlist) after sliding the window.
633  SlideWindow();
634 
635  const double segment_duration_seconds =
636  static_cast<double>(duration) / time_scale_;
637  longest_segment_duration_seconds_ =
638  std::max(longest_segment_duration_seconds_, segment_duration_seconds);
639  bandwidth_estimator_.AddBlock(size, segment_duration_seconds);
640  current_buffer_depth_ += segment_duration_seconds;
641 
642  if (!entries_.empty() &&
643  entries_.back()->type() == HlsEntry::EntryType::kExtInf) {
644  const SegmentInfoEntry* segment_info =
645  static_cast<SegmentInfoEntry*>(entries_.back().get());
646  if (segment_info->start_time() > start_time) {
647  LOG(WARNING)
648  << "Insert a discontinuity tag after the segment with start time "
649  << segment_info->start_time() << " as the next segment starts at "
650  << start_time << ".";
651  entries_.emplace_back(new DiscontinuityEntry());
652  }
653  }
654 
655  entries_.emplace_back(new SegmentInfoEntry(
656  segment_file_name, start_time, segment_duration_seconds, use_byte_range_,
657  start_byte_offset, size, previous_segment_end_offset_));
658  previous_segment_end_offset_ = start_byte_offset + size - 1;
659 }
660 
661 void MediaPlaylist::AdjustLastSegmentInfoEntryDuration(int64_t next_timestamp) {
662  if (time_scale_ == 0)
663  return;
664 
665  const double next_timestamp_seconds =
666  static_cast<double>(next_timestamp) / time_scale_;
667 
668  for (auto iter = entries_.rbegin(); iter != entries_.rend(); ++iter) {
669  if (iter->get()->type() == HlsEntry::EntryType::kExtInf) {
670  SegmentInfoEntry* segment_info =
671  reinterpret_cast<SegmentInfoEntry*>(iter->get());
672 
673  const double segment_duration_seconds =
674  next_timestamp_seconds -
675  static_cast<double>(segment_info->start_time()) / time_scale_;
676  // It could be negative if timestamp messed up.
677  if (segment_duration_seconds > 0)
678  segment_info->set_duration_seconds(segment_duration_seconds);
679  longest_segment_duration_seconds_ =
680  std::max(longest_segment_duration_seconds_, segment_duration_seconds);
681  break;
682  }
683  }
684 }
685 
686 // TODO(kqyang): Right now this class manages the segments including the
687 // deletion of segments when it is no longer needed. However, this class does
688 // not have access to the segment file paths, which is already translated to
689 // segment URLs by HlsNotifier. We have to re-generate segment file paths from
690 // segment template here in order to delete the old segments.
691 // To make the pipeline cleaner, we should move all file manipulations including
692 // segment management to an intermediate layer between HlsNotifier and
693 // MediaPlaylist.
694 void MediaPlaylist::SlideWindow() {
695  if (hls_params_.time_shift_buffer_depth <= 0.0 ||
696  hls_params_.playlist_type != HlsPlaylistType::kLive) {
697  return;
698  }
699  DCHECK_GT(time_scale_, 0);
700 
701  if (current_buffer_depth_ <= hls_params_.time_shift_buffer_depth)
702  return;
703 
704  // Temporary list to hold the EXT-X-KEYs. For example, this allows us to
705  // remove <3> without removing <1> and <2> below (<1> and <2> are moved to the
706  // temporary list and added back later).
707  // #EXT-X-KEY <1>
708  // #EXT-X-KEY <2>
709  // #EXTINF <3>
710  // #EXTINF <4>
711  std::list<std::unique_ptr<HlsEntry>> ext_x_keys;
712  // Consecutive key entries are either fully removed or not removed at all.
713  // Keep track of entry types so we know if it is consecutive key entries.
714  HlsEntry::EntryType prev_entry_type = HlsEntry::EntryType::kExtInf;
715 
716  std::list<std::unique_ptr<HlsEntry>>::iterator last = entries_.begin();
717  for (; last != entries_.end(); ++last) {
718  HlsEntry::EntryType entry_type = last->get()->type();
719  if (entry_type == HlsEntry::EntryType::kExtKey) {
720  if (prev_entry_type != HlsEntry::EntryType::kExtKey)
721  ext_x_keys.clear();
722  ext_x_keys.push_back(std::move(*last));
723  } else if (entry_type == HlsEntry::EntryType::kExtDiscontinuity) {
724  ++discontinuity_sequence_number_;
725  } else {
726  DCHECK_EQ(static_cast<int>(entry_type),
727  static_cast<int>(HlsEntry::EntryType::kExtInf));
728 
729  const SegmentInfoEntry& segment_info =
730  *reinterpret_cast<SegmentInfoEntry*>(last->get());
731  // Remove the current segment only if it falls completely out of time
732  // shift buffer range.
733  const bool segment_within_time_shift_buffer =
734  current_buffer_depth_ - segment_info.duration_seconds() <
735  hls_params_.time_shift_buffer_depth;
736  if (segment_within_time_shift_buffer)
737  break;
738  current_buffer_depth_ -= segment_info.duration_seconds();
739  RemoveOldSegment(segment_info.start_time());
740  media_sequence_number_++;
741  }
742  prev_entry_type = entry_type;
743  }
744  entries_.erase(entries_.begin(), last);
745  // Add key entries back.
746  entries_.insert(entries_.begin(), std::make_move_iterator(ext_x_keys.begin()),
747  std::make_move_iterator(ext_x_keys.end()));
748 }
749 
750 void MediaPlaylist::RemoveOldSegment(int64_t start_time) {
751  if (hls_params_.preserved_segments_outside_live_window == 0)
752  return;
753  if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly)
754  return;
755 
756  segments_to_be_removed_.push_back(media::GetSegmentName(
757  media_info_.segment_template(), start_time, media_sequence_number_ + 1,
758  media_info_.bandwidth()));
759  while (segments_to_be_removed_.size() >
760  hls_params_.preserved_segments_outside_live_window) {
761  VLOG(2) << "Deleting " << segments_to_be_removed_.front();
762  if (!File::Delete(segments_to_be_removed_.front().c_str())) {
763  LOG(WARNING) << "Failed to delete " << segments_to_be_removed_.front()
764  << "; Will retry later.";
765  break;
766  }
767  segments_to_be_removed_.pop_front();
768  }
769 }
770 
771 } // namespace hls
772 } // namespace shaka
void AddBlock(uint64_t size_in_bytes, double duration)
virtual bool GetAC4ImsFlag() const
void SetStreamTypeForTesting(MediaPlaylistStreamType stream_type)
For testing only.
const std::string & language() const
virtual void AddEncryptionInfo(EncryptionMethod method, const std::string &url, const std::string &key_id, const std::string &iv, const std::string &key_format, const std::string &key_format_versions)
void SetCharacteristicsForTesting(const std::vector< std::string > &characteristics)
For testing only.
virtual void AddKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size)
virtual double GetFrameRate() const
virtual uint64_t AvgBitrate() const
virtual bool GetDisplayResolution(uint32_t *width, uint32_t *height) const
virtual double GetLongestSegmentDuration() const
virtual int GetEC3JocComplexity() const
virtual uint64_t MaxBitrate() const
void SetLanguageForTesting(const std::string &language)
For testing only.
virtual int GetNumChannels() const
virtual bool WriteToFile(const std::filesystem::path &file_path)
virtual std::string GetVideoRange() const
void SetCodecForTesting(const std::string &codec)
For testing only.
virtual void AddPlacementOpportunity()
void AddEncryptionInfoForTesting(MediaPlaylist::EncryptionMethod method, const std::string &url, const std::string &key_id, const std::string &iv, const std::string &key_format, const std::string &key_format_versions)
For testing only.
virtual bool SetMediaInfo(const MediaInfo &media_info)
virtual void SetTargetDuration(int32_t target_duration)
virtual bool GetAC4CbiFlag() const
void SetForcedSubtitleForTesting(const bool forced_subtitle)
For testing only.
virtual void SetSampleDuration(int32_t sample_duration)
virtual void AddSegment(const std::string &file_name, int64_t start_time, int64_t duration, uint64_t start_byte_offset, uint64_t size)
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66
std::string LanguageToShortestForm(const std::string &language)