1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/hls_manifest_demuxer_engine.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_refptr.h"
11 #include "base/task/bind_post_task.h"
12 #include "base/task/sequenced_task_runner.h"
13 #include "base/time/time.h"
14 #include "media/base/audio_codecs.h"
15 #include "media/base/media_log.h"
16 #include "media/base/media_track.h"
17 #include "media/base/pipeline_status.h"
18 #include "media/base/supported_types.h"
19 #include "media/base/video_codecs.h"
20 #include "media/filters/manifest_demuxer.h"
21 #include "media/formats/hls/audio_rendition.h"
22 #include "media/formats/hls/media_playlist.h"
23 #include "media/formats/hls/multivariant_playlist.h"
24 #include "media/formats/hls/parse_status.h"
25 #include "media/formats/hls/types.h"
26 #include "media/formats/hls/variant_stream.h"
27 #include "third_party/abseil-cpp/absl/types/optional.h"
33 constexpr const char* kPrimary = "primary";
34 constexpr const char* kAudioOverride = "audio-override";
36 bool ParseAudioCodec(const std::string& codec, AudioType* audio_type) {
37 audio_type->codec = StringToAudioCodec(codec);
38 audio_type->profile = AudioCodecProfile::kUnknown;
39 audio_type->spatial_rendering = false;
40 return audio_type->codec != AudioCodec::kUnknown;
43 bool AreAllAudioCodecsSupported(std::vector<AudioType> audio_types) {
44 if (audio_types.empty()) {
47 for (const auto& type : audio_types) {
48 if (!IsSupportedAudioType(type)) {
55 bool AreAllVideoCodecsSupported(std::vector<VideoType> video_types) {
56 if (video_types.empty()) {
59 for (const auto& type : video_types) {
60 if (!IsSupportedVideoType(type)) {
67 hls::RenditionSelector::CodecSupportType GetSupportedTypes(
68 base::StringPiece container,
69 base::span<const std::string> codecs) {
70 std::vector<VideoType> video_formats;
71 std::vector<AudioType> audio_formats;
72 for (const std::string& codec : codecs) {
73 // Try parsing it as a video codec first, which will set `video.codec`
74 // to unknown if it fails.
77 video.hdr_metadata_type = gfx::HdrMetadataType::kNone;
78 ParseCodec(codec, video.codec, video.profile, video_level,
80 if (video.codec != VideoCodec::kUnknown) {
81 video.level = video_level;
82 video_formats.push_back(video);
87 if (ParseAudioCodec(codec, &audio)) {
88 audio_formats.push_back(audio);
92 bool audio_support = AreAllAudioCodecsSupported(std::move(audio_formats));
93 bool video_support = AreAllVideoCodecsSupported(std::move(video_formats));
95 if (audio_support && video_support) {
96 return hls::RenditionSelector::CodecSupportType::kSupportedAudioVideo;
99 return hls::RenditionSelector::CodecSupportType::kSupportedAudioOnly;
102 return hls::RenditionSelector::CodecSupportType::kSupportedVideoOnly;
104 return hls::RenditionSelector::CodecSupportType::kUnsupported;
109 HlsManifestDemuxerEngine::~HlsManifestDemuxerEngine() = default;
111 HlsManifestDemuxerEngine::HlsManifestDemuxerEngine(
112 base::SequenceBound<HlsDataSourceProvider> dsp,
113 scoped_refptr<base::SequencedTaskRunner> media_task_runner,
114 GURL root_playlist_uri,
116 : data_source_provider_(std::move(dsp)),
117 media_task_runner_(std::move(media_task_runner)),
118 root_playlist_uri_(std::move(root_playlist_uri)),
119 media_log_(media_log->Clone()) {
120 // This is always created on the main sequence, but used on the media sequence
121 DETACH_FROM_SEQUENCE(media_sequence_checker_);
124 HlsManifestDemuxerEngine::PlaylistParseInfo::PlaylistParseInfo(
126 std::vector<std::string> codecs,
128 bool allow_multivariant_playlist)
129 : uri(std::move(uri)),
130 codecs(std::move(codecs)),
131 role(std::move(role)),
132 allow_multivariant_playlist(allow_multivariant_playlist) {}
134 HlsManifestDemuxerEngine::PlaylistParseInfo::~PlaylistParseInfo() {}
136 HlsManifestDemuxerEngine::PlaylistParseInfo::PlaylistParseInfo(
137 const PlaylistParseInfo& copy) = default;
139 std::string HlsManifestDemuxerEngine::GetName() const {
140 return "HlsManifestDemuxer";
143 void HlsManifestDemuxerEngine::InitializeWithMockCodecDetectorForTesting(
144 ManifestDemuxerEngineHost* host,
145 PipelineStatusCallback cb,
146 std::unique_ptr<HlsCodecDetector> codec_detector) {
147 InitializeWithCodecDetector(host, std::move(cb), std::move(codec_detector));
150 void HlsManifestDemuxerEngine::InitializeWithCodecDetector(
151 ManifestDemuxerEngineHost* host,
152 PipelineStatusCallback status_cb,
153 std::unique_ptr<HlsCodecDetector> codec_detector) {
154 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
156 // Initialize the codec detector on the media thread.
157 codec_detector_ = std::move(codec_detector);
159 PlaylistParseInfo parse_info(root_playlist_uri_, {}, kPrimary,
160 /*allow_multivariant_playlist=*/true);
161 ReadFromUrl(root_playlist_uri_, false, absl::nullopt,
162 base::BindOnce(&HlsManifestDemuxerEngine::ParsePlaylist,
163 weak_factory_.GetWeakPtr(), std::move(status_cb),
164 std::move(parse_info)));
167 void HlsManifestDemuxerEngine::Initialize(ManifestDemuxerEngineHost* host,
168 PipelineStatusCallback status_cb) {
169 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
170 InitializeWithCodecDetector(
171 host, std::move(status_cb),
172 std::make_unique<HlsCodecDetectorImpl>(media_log_.get(), this));
175 void HlsManifestDemuxerEngine::OnTimeUpdate(base::TimeDelta time,
176 double playback_rate,
177 ManifestDemuxer::DelayCallback cb) {
178 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
179 if (renditions_.empty()) {
180 std::move(cb).Run(kNoTimestamp);
184 CheckStateAtIndex(time, playback_rate, std::move(cb), 0, absl::nullopt);
187 void HlsManifestDemuxerEngine::CheckStateAtIndex(
188 base::TimeDelta media_time,
189 double playback_rate,
190 ManifestDemuxer::DelayCallback cb,
191 size_t rendition_index,
192 absl::optional<base::TimeDelta> response_time) {
193 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
194 if (rendition_index >= renditions_.size()) {
195 // The response time collected at this point _must_ be valid.
196 std::move(cb).Run(response_time.value());
200 auto recurse = base::BindOnce(
201 &HlsManifestDemuxerEngine::CheckStateAtIndex, weak_factory_.GetWeakPtr(),
202 media_time, playback_rate, std::move(cb), rendition_index + 1);
204 auto on_reply = base::BindOnce(
205 &HlsManifestDemuxerEngine::OnStateChecked, weak_factory_.GetWeakPtr(),
206 base::TimeTicks::Now(), response_time, std::move(recurse));
208 renditions_[rendition_index]->CheckState(media_time, playback_rate,
209 std::move(on_reply));
212 void HlsManifestDemuxerEngine::OnStateChecked(
213 base::TimeTicks call_start,
214 absl::optional<base::TimeDelta> prior_delay,
215 base::OnceCallback<void(absl::optional<base::TimeDelta>)> cb,
216 base::TimeDelta delay_time) {
217 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
218 if (prior_delay.value_or(kNoTimestamp) == kNoTimestamp) {
219 std::move(cb).Run(delay_time);
223 base::TimeDelta spent_duration = base::TimeTicks::Now() - call_start;
224 if (spent_duration > prior_delay.value()) {
225 // Some previous rendition requested a delay that we've already spent while
226 // calculating the delay for the current rendition. Going forward then,
227 // we want to have no delay.
228 std::move(cb).Run(base::Seconds(0));
232 auto adjusted_prior_delay = prior_delay.value() - spent_duration;
233 if (delay_time == kNoTimestamp) {
234 std::move(cb).Run(adjusted_prior_delay);
238 std::move(cb).Run(adjusted_prior_delay > delay_time ? delay_time
239 : adjusted_prior_delay);
242 void HlsManifestDemuxerEngine::Seek(base::TimeDelta time,
243 ManifestDemuxer::SeekCallback cb) {
244 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
245 if (!data_source_provider_) {
246 // The pipeline can call Seek just after an error was surfaced. The error
247 // handler resets |data_source_provider_|, so we should just reply with
248 // another error here.
249 std::move(cb).Run(PIPELINE_ERROR_ABORT);
252 data_source_provider_.AsyncCall(&HlsDataSourceProvider::AbortPendingReads)
253 .WithArgs(base::BindPostTaskToCurrentDefault(
254 base::BindOnce(&HlsManifestDemuxerEngine::ContinueSeekInternal,
255 weak_factory_.GetWeakPtr(), time, std::move(cb))));
258 void HlsManifestDemuxerEngine::ContinueSeekInternal(
259 base::TimeDelta time,
260 ManifestDemuxer::SeekCallback cb) {
261 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
262 bool buffers_needed = false;
263 for (auto& rendition : renditions_) {
264 auto response = rendition->Seek(time);
265 if (!response.has_value()) {
266 std::move(cb).Run(std::move(response).error().AddHere());
270 (ManifestDemuxer::SeekState::kNeedsData == std::move(response).value());
272 std::move(cb).Run(buffers_needed ? ManifestDemuxer::SeekState::kNeedsData
273 : ManifestDemuxer::SeekState::kIsReady);
276 void HlsManifestDemuxerEngine::StartWaitingForSeek() {
277 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
278 for (auto& rendition : renditions_) {
279 rendition->StartWaitingForSeek();
283 void HlsManifestDemuxerEngine::AbortPendingReads() {
284 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
287 bool HlsManifestDemuxerEngine::IsSeekable() const {
288 // `IsSeekable()` is only called after the pipeline has completed successfully
289 // or the initialization step fails. If init fails, we should report that the
290 // player is seekable to keep consistent behavior with other players.
291 return is_seekable_.value_or(true);
294 int64_t HlsManifestDemuxerEngine::GetMemoryUsage() const {
295 // TODO(crbug/1266991): Sum the memory of the renditions and data source
300 void HlsManifestDemuxerEngine::Stop() {
301 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
303 for (auto& rendition : renditions_) {
307 data_source_provider_.Reset();
308 weak_factory_.InvalidateWeakPtrs();
310 multivariant_root_.reset();
311 rendition_selector_.reset();
316 void HlsManifestDemuxerEngine::Abort(HlsDemuxerStatus status) {
317 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
321 host_->OnError({PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_PARSE,
325 void HlsManifestDemuxerEngine::Abort(hls::ParseStatus status) {
326 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
330 host_->OnError({PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_PARSE,
334 void HlsManifestDemuxerEngine::Abort(HlsDataSourceProvider::ReadStatus status) {
335 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
340 {PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_OPEN, std::move(status)});
343 void HlsManifestDemuxerEngine::ReadUntilExhausted(
344 HlsDataSourceProvider::ReadCb cb,
345 HlsDataSourceProvider::ReadStatus::Or<std::unique_ptr<HlsDataSourceStream>>
347 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
348 if (!result.has_value()) {
349 std::move(cb).Run(std::move(result).error());
352 auto stream = std::move(result).value();
353 if (!stream->CanReadMore()) {
354 std::move(cb).Run(std::move(stream));
358 ReadStream(std::move(stream),
359 base::BindOnce(&HlsManifestDemuxerEngine::ReadUntilExhausted,
360 weak_factory_.GetWeakPtr(), std::move(cb)));
363 void HlsManifestDemuxerEngine::ReadFromUrl(
366 absl::optional<hls::types::ByteRange> range,
367 HlsDataSourceProvider::ReadCb cb) {
368 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
369 if (!data_source_provider_) {
370 std::move(cb).Run(HlsDataSourceProvider::ReadStatus::Codes::kAborted);
375 cb = base::BindOnce(&HlsManifestDemuxerEngine::ReadUntilExhausted,
376 weak_factory_.GetWeakPtr(), std::move(cb));
379 data_source_provider_.AsyncCall(&HlsDataSourceProvider::ReadFromUrl)
380 .WithArgs(std::move(uri), range,
381 base::BindPostTaskToCurrentDefault(std::move(cb)));
384 void HlsManifestDemuxerEngine::ReadStream(
385 std::unique_ptr<HlsDataSourceStream> stream,
386 HlsDataSourceProvider::ReadCb cb) {
387 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
388 if (!data_source_provider_) {
389 std::move(cb).Run(HlsDataSourceProvider::ReadStatus::Codes::kAborted);
392 data_source_provider_
393 .AsyncCall(&HlsDataSourceProvider::ReadFromExistingStream)
394 .WithArgs(std::move(stream),
395 base::BindPostTaskToCurrentDefault(std::move(cb)));
398 void HlsManifestDemuxerEngine::ParsePlaylist(
399 PipelineStatusCallback parse_complete_cb,
400 PlaylistParseInfo parse_info,
401 HlsDataSourceProvider::ReadResult m_stream) {
402 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
403 if (!m_stream.has_value()) {
404 return Abort(std::move(m_stream).error().AddHere());
406 auto stream = std::move(m_stream).value();
408 // A four hour movie manifest is ~100Kb.
409 if (stream->buffer_size() > 102400) {
410 MEDIA_LOG(WARNING, media_log_)
411 << "Large Manifest detected: " << stream->buffer_size();
414 auto m_info = hls::Playlist::IdentifyPlaylist(stream->AsString());
415 if (!m_info.has_value()) {
416 return Abort(std::move(m_info).error().AddHere());
419 switch ((*m_info).kind) {
420 case hls::Playlist::Kind::kMultivariantPlaylist: {
421 if (!parse_info.allow_multivariant_playlist) {
422 return Abort(HlsDemuxerStatus::Codes::kRecursiveMultivariantPlaylists);
424 auto playlist = hls::MultivariantPlaylist::Parse(
425 stream->AsString(), parse_info.uri, (*m_info).version);
426 if (!playlist.has_value()) {
427 return Abort(std::move(playlist).error().AddHere());
429 return OnMultivariantPlaylist(std::move(parse_complete_cb),
430 std::move(playlist).value());
432 case hls::Playlist::Kind::kMediaPlaylist: {
433 auto playlist = ParseMediaPlaylistFromStringSource(
434 stream->AsString(), parse_info.uri, (*m_info).version);
435 if (!playlist.has_value()) {
436 return Abort(std::move(playlist).error().AddHere());
438 return OnMediaPlaylist(std::move(parse_complete_cb),
439 std::move(parse_info),
440 std::move(playlist).value());
445 hls::ParseStatus::Or<scoped_refptr<hls::MediaPlaylist>>
446 HlsManifestDemuxerEngine::ParseMediaPlaylistFromStringSource(
447 base::StringPiece source,
449 hls::types::DecimalInteger version) {
450 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
451 return hls::MediaPlaylist::Parse(source, uri, version,
452 multivariant_root_.get());
455 void HlsManifestDemuxerEngine::AddRenditionForTesting(
456 std::unique_ptr<HlsRendition> test_rendition) {
457 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
458 bool is_seekable = test_rendition->GetDuration().has_value();
459 CHECK_EQ(is_seekable_.value_or(is_seekable), is_seekable);
460 is_seekable_ = is_seekable;
461 renditions_.push_back(std::move(test_rendition));
464 void HlsManifestDemuxerEngine::OnMultivariantPlaylist(
465 PipelineStatusCallback parse_complete_cb,
466 scoped_refptr<hls::MultivariantPlaylist> playlist) {
467 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
468 CHECK(!rendition_selector_);
469 multivariant_root_ = std::move(playlist);
470 rendition_selector_ = std::make_unique<hls::RenditionSelector>(
471 multivariant_root_, base::BindRepeating(&GetSupportedTypes));
473 hls::RenditionSelector::PreferredVariants streams =
474 rendition_selector_->GetPreferredVariants(video_preferences_,
477 // Possible outcomes of the rendition selector:
478 // | AOVariant | SelVariant | AORend | primary=? | secondary=? |
479 // |-----------|------------|---------|-----------|-------------|
480 // | null | null | null | X | X |
481 // |-----------|------------|---------|-----------|-------------|
482 // | null | present | null | SV | X |
483 // |-----------|------------|---------|-----------|-------------|
484 // | present | null | present | AOV | X |
485 // |-----------|------------|---------|-----------|-------------|
486 // | present | present | null | SV | X |
487 // |-----------|------------|---------|-----------|-------------|
488 // | present | present | present | SV | AOV |
489 // |-----------|------------|---------|-----------|-------------|
490 absl::optional<GURL> audio_override_uri;
491 const GURL& primary_uri = streams.selected_variant->GetPrimaryRenditionUri();
492 if (streams.audio_override_rendition) {
493 CHECK_NE(streams.audio_override_variant, nullptr);
494 audio_override_uri = streams.audio_override_rendition->GetUri().value_or(
495 streams.audio_override_variant->GetPrimaryRenditionUri());
498 std::vector<PlaylistParseInfo> renditions_to_parse;
499 std::vector<std::string> no_codecs;
501 if (streams.selected_variant) {
502 renditions_to_parse.emplace_back(
503 streams.selected_variant->GetPrimaryRenditionUri(),
504 streams.selected_variant->GetCodecs().value_or(no_codecs), kPrimary);
506 if (streams.audio_override_rendition &&
507 primary_uri != audio_override_uri.value_or(primary_uri)) {
508 CHECK_NE(streams.audio_override_variant, nullptr);
509 renditions_to_parse.emplace_back(
511 streams.audio_override_variant->GetCodecs().value_or(no_codecs),
514 } else if (streams.audio_override_rendition &&
515 primary_uri != audio_override_uri.value_or(primary_uri)) {
516 renditions_to_parse.emplace_back(
518 streams.audio_override_variant->GetCodecs().value_or(no_codecs),
521 Abort(HlsDemuxerStatus::Codes::kNoRenditions);
525 SetStreams(std::move(renditions_to_parse), std::move(parse_complete_cb),
529 void HlsManifestDemuxerEngine::SetStreams(
530 std::vector<PlaylistParseInfo> playlists,
531 PipelineStatusCallback cb,
532 PipelineStatus exit_on_error) {
533 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
534 if (!exit_on_error.is_ok() || playlists.empty()) {
535 // We've either hit the end of the list with a success, or have errored out
536 // early. Either way, the status should be forwarded to the cb.
537 std::move(cb).Run(std::move(exit_on_error));
541 const PlaylistParseInfo playlist = playlists.back();
542 playlists.pop_back();
543 PipelineStatusCallback on_parsed = base::BindOnce(
544 &HlsManifestDemuxerEngine::SetStreams, weak_factory_.GetWeakPtr(),
545 std::move(playlists), std::move(cb));
547 GURL uri = playlist.uri;
548 ReadFromUrl(uri, false, absl::nullopt,
549 base::BindOnce(&HlsManifestDemuxerEngine::ParsePlaylist,
550 weak_factory_.GetWeakPtr(), std::move(on_parsed),
551 std::move(playlist)));
554 void HlsManifestDemuxerEngine::OnMediaPlaylist(
555 PipelineStatusCallback parse_complete_cb,
556 PlaylistParseInfo parse_info,
557 scoped_refptr<hls::MediaPlaylist> playlist) {
558 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
559 hls::MediaPlaylist* playlist_ptr = playlist.get();
560 DetermineStreamContainerAndCodecs(
562 base::BindOnce(&HlsManifestDemuxerEngine::OnPlaylistContainerDetermined,
563 weak_factory_.GetWeakPtr(), std::move(parse_complete_cb),
564 std::move(parse_info), std::move(playlist)));
567 void HlsManifestDemuxerEngine::OnPlaylistContainerDetermined(
568 PipelineStatusCallback parse_complete_cb,
569 PlaylistParseInfo parse_info,
570 scoped_refptr<hls::MediaPlaylist> playlist,
571 HlsDemuxerStatus::Or<HlsCodecDetector::ContainerAndCodecs> maybe_info) {
572 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
573 if (!maybe_info.has_value()) {
574 std::move(parse_complete_cb)
575 .Run({DEMUXER_ERROR_COULD_NOT_OPEN, std::move(maybe_info).error()});
579 auto container_and_codecs = std::move(maybe_info).value();
580 std::string container = std::move(container_and_codecs.container);
581 std::string codecs = std::move(container_and_codecs.codecs);
583 if (parse_info.codecs.size()) {
584 // Codecs came from a multivariant playlist rather than from detection. We
585 // want to join whatever came from the playlist and use that instead.
586 std::stringstream codecstream;
587 std::copy(parse_info.codecs.begin(), parse_info.codecs.end(),
588 std::ostream_iterator<std::string>(codecstream, ", "));
589 codecs = codecstream.str();
591 // codecs ends with a trailing ", " now, so we need to drop that
592 codecs = codecs.substr(0, codecs.length() - 2);
595 if (!host_->AddRole(parse_info.role, container, codecs)) {
596 std::move(parse_complete_cb).Run(DEMUXER_ERROR_COULD_NOT_OPEN);
600 host_->SetSequenceMode(parse_info.role, true);
602 auto m_rendition = HlsRendition::CreateRendition(
603 host_, this, parse_info.role, std::move(playlist), parse_info.uri);
605 if (!m_rendition.has_value()) {
606 std::move(parse_complete_cb)
607 .Run({DEMUXER_ERROR_COULD_NOT_PARSE,
608 std::move(m_rendition).error().AddHere()});
612 auto rendition = std::move(m_rendition).value();
614 if (parse_info.role == kPrimary) {
615 auto duration_or_live = rendition->GetDuration();
616 if (duration_or_live.has_value()) {
617 host_->SetDuration(duration_or_live->InSecondsF());
621 bool seekable = rendition->GetDuration().has_value();
622 if (is_seekable_.value_or(seekable) != seekable) {
623 std::move(parse_complete_cb).Run(DEMUXER_ERROR_COULD_NOT_PARSE);
626 is_seekable_ = seekable;
627 renditions_.push_back(std::move(rendition));
628 std::move(parse_complete_cb).Run(OkStatus());
631 void HlsManifestDemuxerEngine::DetermineStreamContainerAndCodecs(
632 hls::MediaPlaylist* playlist,
633 HlsDemuxerStatusCb<HlsCodecDetector::ContainerAndCodecs> container_cb) {
634 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
635 const auto& segments = playlist->GetSegments();
636 if (segments.empty()) {
637 std::move(container_cb).Run(HlsDemuxerStatus::Codes::kUnsupportedContainer);
642 segments[0]->GetUri(), true, segments[0]->GetByteRange(),
643 base::BindOnce(&HlsManifestDemuxerEngine::PeekFirstSegment,
644 weak_factory_.GetWeakPtr(), std::move(container_cb)));
647 void HlsManifestDemuxerEngine::PeekFirstSegment(
648 HlsDemuxerStatusCb<HlsCodecDetector::ContainerAndCodecs> cb,
649 HlsDataSourceProvider::ReadResult maybe_stream) {
650 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
651 if (!maybe_stream.has_value()) {
652 std::move(cb).Run(HlsDemuxerStatus::Codes::kInvalidSegmentUri);
655 codec_detector_->DetermineContainerAndCodec(std::move(maybe_stream).value(),
658 void HlsManifestDemuxerEngine::OnChunkDemuxerParseWarning(
660 SourceBufferParseWarning warning) {
661 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
662 MEDIA_LOG(WARNING, media_log_)
663 << "ParseWarning (" << role << "): " << static_cast<int>(warning);
666 void HlsManifestDemuxerEngine::OnChunkDemuxerTracksChanged(
668 std::unique_ptr<MediaTracks> tracks) {
669 DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
670 MEDIA_LOG(WARNING, media_log_) << "TracksChanged for role: " << role;