chromium/media/filters/hls_manifest_demuxer_engine.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/hls_manifest_demuxer_engine.h"

#include <optional>
#include <vector>

#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_codecs.h"
#include "media/base/media_log.h"
#include "media/base/media_track.h"
#include "media/base/media_util.h"
#include "media/base/pipeline_status.h"
#include "media/base/supported_types.h"
#include "media/base/video_codec_string_parsers.h"
#include "media/base/video_codecs.h"
#include "media/filters/hls_network_access_impl.h"
#include "media/filters/manifest_demuxer.h"
#include "media/formats/hls/audio_rendition.h"
#include "media/formats/hls/media_playlist.h"
#include "media/formats/hls/multivariant_playlist.h"
#include "media/formats/hls/parse_status.h"
#include "media/formats/hls/types.h"
#include "media/formats/hls/variant_stream.h"
#include "media/formats/mp4/box_reader.h"

namespace media {

namespace {

constexpr const char* kPrimary = "primary";
constexpr const char* kAudioOverride = "audio-override";

bool ParseAudioCodec(const std::string& codec, AudioType* audio_type) {
  audio_type->codec = StringToAudioCodec(codec);
  audio_type->profile = AudioCodecProfile::kUnknown;
  audio_type->spatial_rendering = false;
  return audio_type->codec != AudioCodec::kUnknown;
}

// These functions are intended to test that there are {audio/video} codecs
// present in the types, and that each codec present is supported. An empty
// list of audioo codecs should not be considered "supported audio" for example.
bool AreAllAudioCodecsSupported(const std::vector<AudioType>& audio_types) {
  if (audio_types.empty()) {
    return false;
  }
  for (const auto& type : audio_types) {
    if (!IsSupportedAudioType(type)) {
      return false;
    }
  }
  return true;
}

bool AreAllVideoCodecsSupported(const std::vector<VideoType>& video_types) {
  if (video_types.empty()) {
    return false;
  }
  for (const auto& type : video_types) {
    if (!IsSupportedVideoType(type)) {
      return false;
    }
  }
  return true;
}

hls::RenditionManager::CodecSupportType GetSupportedTypes(
    std::string_view container,
    base::span<const std::string> codecs) {
  std::vector<VideoType> video_formats;
  std::vector<AudioType> audio_formats;
  for (const std::string& codec : codecs) {
    // Try parsing it as a video codec first, which will set `video.codec`
    // to unknown if it fails.
    if (auto result = ParseCodec(codec)) {
      video_formats.push_back({result->codec, result->profile, result->level,
                               result->color_space,
                               gfx::HdrMetadataType::kNone});

      continue;
    }

    AudioType audio;
    if (ParseAudioCodec(codec, &audio)) {
      audio_formats.push_back(audio);
    }
  }

  const bool audio_support = AreAllAudioCodecsSupported(audio_formats);
  const bool video_support = AreAllVideoCodecsSupported(video_formats);

  if (audio_support && video_support) {
    return hls::RenditionManager::CodecSupportType::kSupportedAudioVideo;
  }
  if (audio_support && video_formats.empty()) {
    return hls::RenditionManager::CodecSupportType::kSupportedAudioOnly;
  }
  if (video_support && audio_formats.empty()) {
    return hls::RenditionManager::CodecSupportType::kSupportedVideoOnly;
  }
  return hls::RenditionManager::CodecSupportType::kUnsupported;
}

HlsDemuxerStatus::Or<RelaxedParserSupportedType> CheckMP4Bytes(
    const uint8_t* data,
    size_t size) {
  NullMediaLog null;
  std::unique_ptr<mp4::BoxReader> reader;
  auto result = mp4::BoxReader::ReadTopLevelBox(data, size, &null, &reader);
  if (result == mp4::ParseResult::kOk) {
    return RelaxedParserSupportedType::kMP4;
  }
  return HlsDemuxerStatus::Codes::kUnsupportedContainer;
}

HlsDemuxerStatus::Or<RelaxedParserSupportedType>
CheckBitstreamForContainerMagic(const uint8_t* data, size_t size) {
  CHECK_GT(size, 0lu);

  constexpr uint8_t kMP4FirstByte = 0x66;
  constexpr uint8_t kMPEGTSFirstByte = 0x47;
  constexpr uint8_t kFMP4FirstByte = 0x00;
  constexpr uint8_t kAACFirstByte = 0xFF;
  constexpr uint8_t kID3FirstByte = 0x49;

  switch (data[0]) {
    case kMP4FirstByte:
    case kFMP4FirstByte: {
      return CheckMP4Bytes(data, size);
    }
    case kID3FirstByte:
    case kAACFirstByte: {
      // TODO(issue/40253609): Check further bytes in the header.
      return RelaxedParserSupportedType::kAAC;
    }
    case kMPEGTSFirstByte: {
      return RelaxedParserSupportedType::kMP2T;
    }
    default: {
      return HlsDemuxerStatus::Codes::kUnsupportedContainer;
    }
  }
}

}  // namespace

HlsManifestDemuxerEngine::~HlsManifestDemuxerEngine() = default;

HlsManifestDemuxerEngine::HlsManifestDemuxerEngine(
    base::SequenceBound<HlsDataSourceProvider> dsp,
    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
    bool was_already_tainted,
    GURL root_playlist_uri,
    MediaLog* media_log)
    : media_task_runner_(std::move(media_task_runner)),
      root_playlist_uri_(std::move(root_playlist_uri)),
      media_log_(media_log->Clone()),
      network_access_(std::make_unique<HlsNetworkAccessImpl>(std::move(dsp))),
      origin_tainted_(was_already_tainted) {
  // This is always created on the main sequence, but used on the media sequence
  DETACH_FROM_SEQUENCE(media_sequence_checker_);
}

void HlsManifestDemuxerEngine::ProcessActionQueue() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);

  if (action_in_progress_ || pending_action_queue_.empty()) {
    return;
  }

  action_in_progress_ = true;
  auto action = std::move(pending_action_queue_.front());
  pending_action_queue_.pop();
  std::move(action).Run(base::BindOnce(
      &HlsManifestDemuxerEngine::OnActionComplete, weak_factory_.GetWeakPtr()));
}

void HlsManifestDemuxerEngine::OnActionComplete() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  CHECK(action_in_progress_);
  action_in_progress_ = false;
  ProcessActionQueue();
}

HlsManifestDemuxerEngine::PlaylistParseInfo::PlaylistParseInfo(
    GURL uri,
    std::vector<std::string> codecs,
    std::string role,
    bool allow_multivariant_playlist)
    : uri(std::move(uri)),
      codecs(std::move(codecs)),
      role(std::move(role)),
      allow_multivariant_playlist(allow_multivariant_playlist) {}

HlsManifestDemuxerEngine::PlaylistParseInfo::~PlaylistParseInfo() {}

HlsManifestDemuxerEngine::PlaylistParseInfo::PlaylistParseInfo(
    const PlaylistParseInfo& copy) = default;

int64_t HlsManifestDemuxerEngine::GetMemoryUsage() {
  return total_stream_memory_;
}

bool HlsManifestDemuxerEngine::WouldTaintOrigin() {
  return origin_tainted_;
}

bool HlsManifestDemuxerEngine::IsStreaming() {
  return !is_seekable_;
}

std::string HlsManifestDemuxerEngine::GetName() const {
  return "HlsManifestDemuxer";
}

void HlsManifestDemuxerEngine::StartWaitingForSeek() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  for (auto& [_, rendition] : renditions_) {
    rendition->StartWaitingForSeek();
  }
}

void HlsManifestDemuxerEngine::AbortPendingReads(base::OnceClosure cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
}

bool HlsManifestDemuxerEngine::IsSeekable() const {
  // `IsSeekable()` is only called after the pipeline has completed successfully
  // or the initialization step fails. If init fails, we should report that the
  // player is seekable to keep consistent behavior with other players.
  return is_seekable_.value_or(true);
}

int64_t HlsManifestDemuxerEngine::GetMemoryUsage() const {
  // TODO(crbug.com/40057824): Sum the memory of the renditions and data source
  // providers.
  return 0;
}

void HlsManifestDemuxerEngine::Stop() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  pending_action_queue_ = {};

  for (auto& [_, rendition] : renditions_) {
    rendition->Stop();
  }

  network_access_.reset();
  weak_factory_.InvalidateWeakPtrs();

  multivariant_root_.reset();
  rendition_manager_.reset();
  renditions_.clear();
  host_ = nullptr;
}

void HlsManifestDemuxerEngine::Seek(base::TimeDelta time,
                                    ManifestDemuxer::SeekCallback cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (!network_access_) {
    // The pipeline can call Seek just after an error was surfaced. The error
    // handler resets |network_access_|, so we should just reply with
    // another error here.
    std::move(cb).Run(PIPELINE_ERROR_ABORT);
    return;
  }

  ProcessAsyncAction<ManifestDemuxer::SeekResponse>(
      std::move(cb), base::BindOnce(&HlsManifestDemuxerEngine::SeekAction,
                                    weak_factory_.GetWeakPtr(), time));
}

void HlsManifestDemuxerEngine::SeekAction(base::TimeDelta time,
                                          ManifestDemuxer::SeekCallback cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  network_access_->AbortPendingReads(base::BindPostTaskToCurrentDefault(
      base::BindOnce(&HlsManifestDemuxerEngine::ContinueSeekInternal,
                     weak_factory_.GetWeakPtr(), time, std::move(cb))));
}

void HlsManifestDemuxerEngine::ContinueSeekInternal(
    base::TimeDelta time,
    ManifestDemuxer::SeekCallback cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  bool buffers_needed = false;
  for (auto& [_, rendition] : renditions_) {
    auto response = rendition->Seek(time);
    if (!response.has_value()) {
      std::move(cb).Run(std::move(response).error().AddHere());
      return;
    }
    buffers_needed |=
        (ManifestDemuxer::SeekState::kNeedsData == std::move(response).value());
  }
  std::move(cb).Run(buffers_needed ? ManifestDemuxer::SeekState::kNeedsData
                                   : ManifestDemuxer::SeekState::kIsReady);
}

void HlsManifestDemuxerEngine::Initialize(ManifestDemuxerEngineHost* host,
                                          PipelineStatusCallback status_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);

  host_ = host;
  ProcessAsyncAction<PipelineStatus>(
      std::move(status_cb),
      base::BindOnce(&HlsManifestDemuxerEngine::InitAction,
                     weak_factory_.GetWeakPtr()));
}

void HlsManifestDemuxerEngine::InitAction(PipelineStatusCallback status_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  PlaylistParseInfo parse_info(root_playlist_uri_, {}, kPrimary,
                               /*allow_multivariant_playlist=*/true);
  LoadPlaylist(
      parse_info,
      base::BindOnce(&HlsManifestDemuxerEngine::FinishInitialization,
                     weak_factory_.GetWeakPtr(), std::move(status_cb)));
}

void HlsManifestDemuxerEngine::FinishInitialization(PipelineStatusCallback cb,
                                                    PipelineStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  std::move(cb).Run(std::move(status));
}

void HlsManifestDemuxerEngine::OnTimeUpdate(base::TimeDelta time,
                                            double playback_rate,
                                            ManifestDemuxer::DelayCallback cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  CHECK_LE(renditions_.size(), 3lu);
  ProcessAsyncAction<base::TimeDelta>(
      std::move(cb),
      base::BindOnce(&HlsManifestDemuxerEngine::OnTimeUpdateAction,
                     weak_factory_.GetWeakPtr(), time, playback_rate));
}

void HlsManifestDemuxerEngine::OnTimeUpdateAction(
    base::TimeDelta time,
    double playback_rate,
    ManifestDemuxer::DelayCallback cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("media", "HLS::OnTimeUpdate", this);
  cb = base::BindOnce(&HlsManifestDemuxerEngine::FinishTimeUpdate,
                      weak_factory_.GetWeakPtr(), std::move(cb));
  for (const auto& [role, _] : renditions_) {
    cb = base::BindOnce(&HlsManifestDemuxerEngine::CheckState,
                        weak_factory_.GetWeakPtr(), time, playback_rate, role,
                        std::move(cb));
  }
  std::move(cb).Run(kNoTimestamp);
}

void HlsManifestDemuxerEngine::FinishTimeUpdate(
    ManifestDemuxer::DelayCallback cb,
    base::TimeDelta delay_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::OnTimeUpdate", this);
  std::move(cb).Run(std::move(delay_time));
}

void HlsManifestDemuxerEngine::CheckState(base::TimeDelta time,
                                          double playback_rate,
                                          std::string role,
                                          ManifestDemuxer::DelayCallback cb,
                                          base::TimeDelta delay_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  DCHECK(renditions_.contains(role));
  renditions_[role]->CheckState(
      time, playback_rate,
      base::BindOnce(&HlsManifestDemuxerEngine::OnStateChecked,
                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now(),
                     delay_time, std::move(cb)));
}

void HlsManifestDemuxerEngine::OnStateChecked(base::TimeTicks start_time,
                                              base::TimeDelta prior_delay,
                                              ManifestDemuxer::DelayCallback cb,
                                              base::TimeDelta new_delay) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (prior_delay == kNoTimestamp) {
    std::move(cb).Run(new_delay);
    return;
  }

  if (new_delay == kNoTimestamp) {
    std::move(cb).Run(prior_delay);
    return;
  }

  base::TimeDelta spent_duration = base::TimeTicks::Now() - start_time;
  if (prior_delay <= spent_duration) {
    std::move(cb).Run(base::Seconds(0));
    return;
  }

  prior_delay -= spent_duration;
  if (prior_delay < new_delay) {
    std::move(cb).Run(prior_delay);
    return;
  }

  std::move(cb).Run(new_delay);
}

void HlsManifestDemuxerEngine::UpdateRenditionManifestUri(
    std::string role,
    GURL uri,
    base::OnceCallback<void(bool)> cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("media", "HLS::UpdateRenditionManifest",
                                    this, "uri", uri);
  GURL uri_copy = uri;
  ReadManifest(
      std::move(uri_copy),
      base::BindOnce(&HlsManifestDemuxerEngine::UpdateMediaPlaylistForRole,
                     weak_factory_.GetWeakPtr(), std::move(role),
                     std::move(uri), std::move(cb)));
}

void HlsManifestDemuxerEngine::UpdateMediaPlaylistForRole(
    std::string role,
    GURL uri,
    base::OnceCallback<void(bool)> cb,
    HlsDataSourceProvider::ReadResult maybe_stream) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (!maybe_stream.has_value()) {
    Abort(std::move(maybe_stream).error().AddHere());
    std::move(cb).Run(false);
    return;
  }
  auto stream = std::move(maybe_stream).value();

  auto maybe_info = hls::Playlist::IdentifyPlaylist(stream->AsString());
  if (!maybe_info.has_value()) {
    Abort(std::move(maybe_info).error().AddHere());
    std::move(cb).Run(false);
    return;
  }

  if ((*maybe_info).kind != hls::Playlist::Kind::kMediaPlaylist) {
    Abort(HlsDemuxerStatus::Codes::kInvalidManifest);
    std::move(cb).Run(false);
    return;
  }

  auto maybe_playlist = ParseMediaPlaylistFromStringSource(
      stream->AsString(), std::move(uri), (*maybe_info).version);
  if (!maybe_playlist.has_value()) {
    Abort(std::move(maybe_playlist).error().AddHere());
    std::move(cb).Run(false);
    return;
  }
  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::UpdateRenditionManifest",
                                  this);

  renditions_[role]->UpdatePlaylist(std::move(maybe_playlist).value(),
                                    std::nullopt);
  std::move(cb).Run(true);
}

void HlsManifestDemuxerEngine::OnRenditionsReselected(
    hls::AdaptationReason reason,
    const hls::VariantStream* variant,
    const hls::AudioRendition* audio_override_rendition) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  stats_reporter_.OnAdaptation(reason);
  ProcessAsyncAction<PipelineStatus>(
      base::BindOnce(&HlsManifestDemuxerEngine::OnStatus,
                     weak_factory_.GetWeakPtr()),
      base::BindOnce(&HlsManifestDemuxerEngine::AdaptationAction,
                     weak_factory_.GetWeakPtr(), variant,
                     audio_override_rendition));
}

void HlsManifestDemuxerEngine::AdaptationAction(
    const hls::VariantStream* variant,
    const hls::AudioRendition* audio_override_rendition,
    PipelineStatusCallback status_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("media", "HLS::SelectRenditions", this,
                                    "reselect", true);

  OnRenditionsSelected(std::move(status_cb), variant, audio_override_rendition);
}

void HlsManifestDemuxerEngine::Abort(HlsDemuxerStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  OnStatus({PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_PARSE,
            std::move(status)});
}

void HlsManifestDemuxerEngine::Abort(hls::ParseStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  OnStatus({PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_PARSE,
            std::move(status)});
}

void HlsManifestDemuxerEngine::Abort(HlsDataSourceProvider::ReadStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  OnStatus(
      {PipelineStatus::Codes::DEMUXER_ERROR_COULD_NOT_OPEN, std::move(status)});
}

void HlsManifestDemuxerEngine::OnStatus(PipelineStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (host_ && !status.is_ok()) {
    host_->OnError(std::move(status).AddHere());
  }
}

void HlsManifestDemuxerEngine::UpdateHlsDataSourceStats(
    HlsDataSourceProvider::ReadCb cb,
    HlsDataSourceProvider::ReadStatus::Or<std::unique_ptr<HlsDataSourceStream>>
        result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (!result.has_value()) {
    std::move(cb).Run(std::move(result).error().AddHere());
    return;
  }
  auto stream = std::move(result).value();
  origin_tainted_ |= stream->would_taint_origin();
  stats_reporter_.SetWouldTaintOrigin(origin_tainted_);
  total_stream_memory_ = stream->memory_usage();
  std::move(cb).Run(std::move(stream));
}

void HlsManifestDemuxerEngine::ReadKey(
    const hls::MediaSegment::EncryptionData& data,
    HlsDataSourceProvider::ReadCb cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  network_access_->ReadKey(std::move(data), BindStatsUpdate(std::move(cb)));
}

HlsDataSourceProvider::ReadCb HlsManifestDemuxerEngine::BindStatsUpdate(
    HlsDataSourceProvider::ReadCb cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  return base::BindOnce(&HlsManifestDemuxerEngine::UpdateHlsDataSourceStats,
                        weak_factory_.GetWeakPtr(), std::move(cb));
}

void HlsManifestDemuxerEngine::ReadManifest(const GURL& uri,
                                            HlsDataSourceProvider::ReadCb cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  network_access_->ReadManifest(std::move(uri), BindStatsUpdate(std::move(cb)));
}

void HlsManifestDemuxerEngine::ReadMediaSegment(
    const hls::MediaSegment& segment,
    bool read_chunked,
    bool include_init,
    HlsDataSourceProvider::ReadCb cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  network_access_->ReadMediaSegment(segment, read_chunked, include_init,
                                    BindStatsUpdate(std::move(cb)));
}

void HlsManifestDemuxerEngine::ReadStream(
    std::unique_ptr<HlsDataSourceStream> stream,
    HlsDataSourceProvider::ReadCb cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  network_access_->ReadStream(std::move(stream),
                              BindStatsUpdate(std::move(cb)));
}

void HlsManifestDemuxerEngine::UpdateNetworkSpeed(uint64_t bps) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (rendition_manager_) {
    rendition_manager_->UpdateNetworkSpeed(bps);
  }
}

void HlsManifestDemuxerEngine::ParsePlaylist(
    PipelineStatusCallback parse_complete_cb,
    PlaylistParseInfo parse_info,
    HlsDataSourceProvider::ReadResult m_stream) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (!m_stream.has_value()) {
    return Abort(std::move(m_stream).error().AddHere());
  }
  auto stream = std::move(m_stream).value();

  // A four hour movie manifest is ~100Kb.
  if (stream->buffer_size() > 102400) {
    MEDIA_LOG(WARNING, media_log_)
        << "Large Manifest detected: " << stream->buffer_size();
  }

  auto m_info = hls::Playlist::IdentifyPlaylist(stream->AsString());
  if (!m_info.has_value()) {
    return Abort(std::move(m_info).error().AddHere());
  }

  switch ((*m_info).kind) {
    case hls::Playlist::Kind::kMultivariantPlaylist: {
      if (!parse_info.allow_multivariant_playlist) {
        return Abort(HlsDemuxerStatus::Codes::kRecursiveMultivariantPlaylists);
      }
      stats_reporter_.SetIsMultivariantPlaylist(true);
      auto playlist = hls::MultivariantPlaylist::Parse(
          stream->AsString(), parse_info.uri, (*m_info).version);
      if (!playlist.has_value()) {
        return Abort(std::move(playlist).error().AddHere());
      }
      return OnMultivariantPlaylist(std::move(parse_complete_cb),
                                    std::move(playlist).value());
    }
    case hls::Playlist::Kind::kMediaPlaylist: {
      if (parse_info.allow_multivariant_playlist) {
        // Only a root playlist is allowed to be multivariant, so if the root
        // is only a media playlist, then this entire playback is not
        // multivariant.
        stats_reporter_.SetIsMultivariantPlaylist(false);
      }
      auto playlist = ParseMediaPlaylistFromStringSource(
          stream->AsString(), parse_info.uri, (*m_info).version);
      if (!playlist.has_value()) {
        return Abort(std::move(playlist).error().AddHere());
      }
      return OnMediaPlaylist(std::move(parse_complete_cb),
                             std::move(parse_info),
                             std::move(playlist).value());
    }
  }
}

hls::ParseStatus::Or<scoped_refptr<hls::MediaPlaylist>>
HlsManifestDemuxerEngine::ParseMediaPlaylistFromStringSource(
    std::string_view source,
    GURL uri,
    hls::types::DecimalInteger version) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  return hls::MediaPlaylist::Parse(source, uri, version,
                                   multivariant_root_.get());
}

void HlsManifestDemuxerEngine::SetEndOfStream(bool ended) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  // If we receive a notice that the stream is no longer ended, assert that
  // the ended stream count is at least greater than zero for sanity checking.
  CHECK(ended || ended_stream_count_);
  if (ended && (++ended_stream_count_ == renditions_.size())) {
    host_->SetEndOfStream();
  }
  if (!ended && (--ended_stream_count_ == renditions_.size() - 1)) {
    host_->UnsetEndOfStream();
  }
}
void HlsManifestDemuxerEngine::AddRenditionForTesting(
    std::string role,
    std::unique_ptr<HlsRendition> test_rendition) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  bool is_seekable = test_rendition->GetDuration().has_value();
  CHECK_EQ(is_seekable_.value_or(is_seekable), is_seekable);
  is_seekable_ = is_seekable;
  renditions_[role] = std::move(test_rendition);
}

void HlsManifestDemuxerEngine::OnMultivariantPlaylist(
    PipelineStatusCallback parse_complete_cb,
    scoped_refptr<hls::MultivariantPlaylist> playlist) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  CHECK(!rendition_manager_);
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("media", "HLS::SelectRenditions", this);
  multivariant_root_ = std::move(playlist);
  rendition_manager_ = std::make_unique<hls::RenditionManager>(
      multivariant_root_,
      base::BindRepeating(&HlsManifestDemuxerEngine::OnRenditionsReselected,
                          weak_factory_.GetWeakPtr()),
      base::BindRepeating(&GetSupportedTypes));

  if (!rendition_manager_->HasAnyVariants()) {
    // This will abort the pending init, and `parse_complete_cb` will not need
    // to be called.
    Abort(HlsDemuxerStatus::Codes::kNoRenditions);
    return;
  }

  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::LoadPlaylist", this);
  rendition_manager_->Reselect(
      base::BindOnce(&HlsManifestDemuxerEngine::OnRenditionsSelected,
                     weak_factory_.GetWeakPtr(), std::move(parse_complete_cb)));
}

void HlsManifestDemuxerEngine::OnRenditionsSelected(
    PipelineStatusCallback on_complete,
    const hls::VariantStream* variant,
    const hls::AudioRendition* audio_override_rendition) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);

  // Ensure that if the variant changes, then we update the codecs that are
  // expected. There can still be other codecs determined after parsing the
  // media content.
  if (variant) {
    std::vector<std::string> no_codecs;
    selected_variant_codecs_ = variant->GetCodecs().value_or(no_codecs);
  }

  // If nothing was selected, then we are in an unplayable state, regardless
  // of whether this is the first initialization or not.
  if (!audio_override_rendition && !variant) {
    std::move(on_complete).Run(DEMUXER_ERROR_COULD_NOT_OPEN);
    return;
  }

  // Bind the audio override rendition fetch into a closure. If we have to
  // reselect the primary rendition now, this will take the place of the
  // on_complete callback.
  if (audio_override_rendition) {
    PlaylistParseInfo override_parse_info = {
        audio_override_rendition->GetUri().value(), selected_variant_codecs_,
        kAudioOverride};

    on_complete = PipelineStatus::BindOkContinuation(
        std::move(on_complete),
        base::BindOnce(&HlsManifestDemuxerEngine::LoadPlaylist,
                       weak_factory_.GetWeakPtr(),
                       std::move(override_parse_info)));
  }
  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::SelectRenditions", this);

  // If there is a variant change, just call LoadPlaylist directly. Since we've
  // already checked that variant and override are not both null, we need to
  // run the variant load CB.
  if (variant) {
    PlaylistParseInfo primary_parse_info = {variant->GetPrimaryRenditionUri(),
                                            selected_variant_codecs_, kPrimary};
    LoadPlaylist(std::move(primary_parse_info), std::move(on_complete));
  } else {
    std::move(on_complete).Run(PIPELINE_OK);
  }
}

void HlsManifestDemuxerEngine::LoadPlaylist(
    PlaylistParseInfo parse_info,
    PipelineStatusCallback on_complete) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  auto uri = parse_info.uri;
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("media", "HLS::LoadPlaylist", this, "uri",
                                    uri);
  ReadManifest(std::move(uri),
               base::BindOnce(&HlsManifestDemuxerEngine::ParsePlaylist,
                              weak_factory_.GetWeakPtr(),
                              std::move(on_complete), std::move(parse_info)));
}

void HlsManifestDemuxerEngine::OnMediaPlaylist(
    PipelineStatusCallback parse_complete_cb,
    PlaylistParseInfo parse_info,
    scoped_refptr<hls::MediaPlaylist> playlist) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);

  // TODO(crbug.com/40057824) On stream adaptation, if the codecs are not the
  // same, we'll have to re-create the chunk demuxer role. For now, just assume
  // the codecs are the same.
  auto maybe_exists = renditions_.find(parse_info.role);
  if (maybe_exists != renditions_.end()) {
    maybe_exists->second->UpdatePlaylist(std::move(playlist), parse_info.uri);
    TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::LoadPlaylist", this);
    std::move(parse_complete_cb).Run(OkStatus());
    return;
  }

  hls::MediaPlaylist* playlist_ptr = playlist.get();
  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
      "media", "HLS::DetermineStreamContainerAndCodecs", this);
  DetermineStreamContainer(
      playlist_ptr,
      base::BindOnce(&HlsManifestDemuxerEngine::OnStreamContainerDetermined,
                     weak_factory_.GetWeakPtr(), std::move(parse_complete_cb),
                     std::move(parse_info), std::move(playlist)));
}

void HlsManifestDemuxerEngine::OnStreamContainerDetermined(
    PipelineStatusCallback parse_complete_cb,
    PlaylistParseInfo parse_info,
    scoped_refptr<hls::MediaPlaylist> playlist,
    HlsDemuxerStatus::Or<RelaxedParserSupportedType> maybe_mime) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  CHECK(!renditions_.contains(parse_info.role));
  if (!maybe_mime.has_value()) {
    std::move(parse_complete_cb)
        .Run({DEMUXER_ERROR_COULD_NOT_OPEN, std::move(maybe_mime).error()});
    return;
  }

  if (!host_->AddRole(parse_info.role, std::move(maybe_mime).value())) {
    std::move(parse_complete_cb).Run(DEMUXER_ERROR_COULD_NOT_OPEN);
    return;
  }

  host_->SetSequenceMode(parse_info.role, true);

  auto rendition = HlsRendition::CreateRendition(
      host_, this, parse_info.role, std::move(playlist), parse_info.uri);

  if (parse_info.role == kPrimary) {
    auto duration_or_live = rendition->GetDuration();
    if (duration_or_live.has_value()) {
      host_->SetDuration(duration_or_live->InSecondsF());
    }
  }

  bool seekable = rendition->GetDuration().has_value();
  if (is_seekable_.value_or(seekable) != seekable) {
    std::move(parse_complete_cb).Run(DEMUXER_ERROR_COULD_NOT_PARSE);
    return;
  }
  is_seekable_ = seekable;
  stats_reporter_.SetIsLiveContent(!seekable);
  renditions_[parse_info.role] = std::move(rendition);
  TRACE_EVENT_NESTABLE_ASYNC_END0(
      "media", "HLS::DetermineStreamContainerAndCodecs", this);
  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::LoadPlaylist", this);
  std::move(parse_complete_cb).Run(OkStatus());
}

void HlsManifestDemuxerEngine::DetermineStreamContainer(
    hls::MediaPlaylist* playlist,
    HlsDemuxerStatusCb<RelaxedParserSupportedType> container_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  const auto& segments = playlist->GetSegments();
  if (segments.empty()) {
    std::move(container_cb).Run(HlsDemuxerStatus::Codes::kUnsupportedContainer);
    return;
  }

  // In the best case, we can just assert the mime type based on extension,
  // but if it's unrecognized, we have to fetch and parse it.
  const auto first_segment_path = segments[0]->GetUri().path_piece();

  std::optional<RelaxedParserSupportedType> mime = std::nullopt;
  if (first_segment_path.ends_with(".ts")) {
    mime = RelaxedParserSupportedType::kMP2T;
  } else if (first_segment_path.ends_with(".mp4")) {
    mime = RelaxedParserSupportedType::kMP4;
  } else if (first_segment_path.ends_with(".m4v")) {
    mime = RelaxedParserSupportedType::kMP4;
  } else if (first_segment_path.ends_with(".m4s")) {
    mime = RelaxedParserSupportedType::kMP4;
  } else if (first_segment_path.ends_with(".m4a")) {
    mime = RelaxedParserSupportedType::kMP4;
  } else if (first_segment_path.ends_with(".aac")) {
    mime = RelaxedParserSupportedType::kAAC;
  }

  if (mime.has_value()) {
    std::move(container_cb).Run(mime.value());
  } else {
    TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("media", "HLS::PeekSegmentChunk", this,
                                      "uri", segments[0]->GetUri());
    ReadMediaSegment(
        *segments[0], /*read_chunked=*/true, /*include_init=*/true,
        base::BindOnce(&HlsManifestDemuxerEngine::DetermineBitstreamContainer,
                       weak_factory_.GetWeakPtr(), std::move(container_cb)));
  }
}

void HlsManifestDemuxerEngine::DetermineBitstreamContainer(
    HlsDemuxerStatusCb<RelaxedParserSupportedType> cb,
    HlsDataSourceProvider::ReadResult maybe_stream) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  TRACE_EVENT_NESTABLE_ASYNC_END0("media", "HLS::PeekSegmentChunk", this);

  if (!maybe_stream.has_value()) {
    std::move(cb).Run(HlsDemuxerStatus::Codes::kInvalidSegmentUri);
    return;
  }

  auto stream = std::move(maybe_stream).value();
  if (!stream->buffer_size()) {
    std::move(cb).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
    return;
  }

  std::move(cb).Run(CheckBitstreamForContainerMagic(stream->raw_data(),
                                                    stream->buffer_size()));
}

void HlsManifestDemuxerEngine::OnChunkDemuxerParseWarning(
    std::string role,
    SourceBufferParseWarning warning) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  MEDIA_LOG(WARNING, media_log_)
      << "ParseWarning (" << role << "): " << static_cast<int>(warning);
}

void HlsManifestDemuxerEngine::OnChunkDemuxerTracksChanged(
    std::string role,
    std::unique_ptr<MediaTracks> tracks) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  MEDIA_LOG(WARNING, media_log_) << "TracksChanged for role: " << role;
}

}  // namespace media