#include "media/formats/hls/multivariant_playlist.h"
#include <optional>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "media/formats/hls/items.h"
#include "media/formats/hls/parse_status.h"
#include "media/formats/hls/playlist.h"
#include "media/formats/hls/playlist_common.h"
#include "media/formats/hls/quirks.h"
#include "media/formats/hls/rendition.h"
#include "media/formats/hls/source_string.h"
#include "media/formats/hls/tags.h"
#include "media/formats/hls/types.h"
#include "media/formats/hls/variable_dictionary.h"
#include "media/formats/hls/variant_stream.h"
#include "url/gurl.h"
namespace media::hls {
namespace {
template <typename T>
T* GetOrCreateRenditionGroup(
base::PassKey<MultivariantPlaylist> pass_key,
base::flat_map<std::optional<std::string_view>, scoped_refptr<T>>& groups,
std::optional<std::string_view> id) {
auto iter = groups.find(id);
if (iter == groups.end()) {
std::optional<std::string> group_id;
if (id.has_value()) {
group_id = std::string(*id);
}
auto group = base::MakeRefCounted<RenditionGroup>(pass_key, group_id);
iter = groups.insert(std::make_pair(id, std::move(group))).first;
}
return iter->second.get();
}
}
MultivariantPlaylist::~MultivariantPlaylist() = default;
ParseStatus::Or<scoped_refptr<MultivariantPlaylist>>
MultivariantPlaylist::Parse(std::string_view source,
GURL uri,
types::DecimalInteger version) {
DCHECK(version != 0);
if (version < Playlist::kMinSupportedVersion ||
version > Playlist::kMaxSupportedVersion) {
return ParseStatusCode::kPlaylistHasUnsupportedVersion;
}
if (!uri.is_valid()) {
return ParseStatusCode::kInvalidUri;
}
SourceLineIterator src_iter{source};
{
auto m3u_tag_result = CheckM3uTag(&src_iter);
if (!m3u_tag_result.has_value()) {
return std::move(m3u_tag_result).error();
}
}
CommonParserState common_state;
VariableDictionary::SubstitutionBuffer sub_buffer;
std::optional<XStreamInfTag> inf_tag;
std::vector<VariantStream> variants;
base::flat_map<std::optional<std::string_view>, scoped_refptr<RenditionGroup>>
audio_rendition_groups;
RenditionGroup::RenditionTrackId::Generator rendition_id_generator;
while (true) {
auto item_result = GetNextLineItem(&src_iter);
if (!item_result.has_value()) {
auto error = std::move(item_result).error();
if (error.code() == ParseStatusCode::kReachedEOF) {
break;
}
return std::move(error);
}
auto item = std::move(item_result).value();
if (auto* tag = std::get_if<TagItem>(&item)) {
if (inf_tag.has_value()) {
return ParseStatusCode::kXStreamInfTagNotFollowedByUri;
}
if (!tag->GetName().has_value()) {
HandleUnknownTag(*tag);
continue;
}
switch (GetTagKind(*tag->GetName())) {
case TagKind::kCommonTag: {
auto error = ParseCommonTag(*tag, &common_state);
if (error.has_value()) {
return std::move(error).value();
}
continue;
}
case TagKind::kMediaPlaylistTag:
if (!HLSQuirks::AllowMediaTagsInMultivariantPlaylists()) {
return ParseStatusCode::kMultivariantPlaylistHasMediaPlaylistTag;
}
continue;
case TagKind::kMultivariantPlaylistTag:
break;
}
switch (static_cast<MultivariantPlaylistTagName>(*tag->GetName())) {
case MultivariantPlaylistTagName::kXContentSteering: {
break;
}
case MultivariantPlaylistTagName::kXIFrameStreamInf: {
break;
}
case MultivariantPlaylistTagName::kXMedia: {
auto result =
XMediaTag::Parse(*tag, common_state.variable_dict, sub_buffer);
if (!result.has_value()) {
return std::move(result).error();
}
auto media_tag = std::move(result).value();
switch (media_tag.type) {
case MediaType::kAudio: {
auto* group = GetOrCreateRenditionGroup(
{}, audio_rendition_groups, media_tag.group_id.Str());
auto rendition_result = group->AddRendition(
base::PassKey<MultivariantPlaylist>(), std::move(media_tag),
uri, rendition_id_generator.GenerateNextId());
if (!rendition_result.has_value()) {
return std::move(rendition_result).error();
}
break;
}
case MediaType::kVideo: {
break;
}
case MediaType::kSubtitles: {
break;
}
case MediaType::kClosedCaptions: {
break;
}
}
break;
}
case MultivariantPlaylistTagName::kXSessionData: {
break;
}
case MultivariantPlaylistTagName::kXSessionKey: {
break;
}
case MultivariantPlaylistTagName::kXStreamInf: {
auto error = ParseUniqueTag(*tag, inf_tag, common_state.variable_dict,
sub_buffer);
if (error.has_value()) {
return std::move(error).value();
}
break;
}
}
continue;
}
static_assert(std::variant_size<GetNextLineItemResult>() == 2);
auto variant_uri_result = ParseUri(std::get<UriItem>(std::move(item)), uri,
common_state, sub_buffer);
if (!variant_uri_result.has_value()) {
return std::move(variant_uri_result).error();
}
auto variant_uri = std::move(variant_uri_result).value();
if (!inf_tag.has_value()) {
return ParseStatusCode::kVariantMissingStreamInfTag;
}
scoped_refptr<RenditionGroup> audio_renditions;
if (inf_tag->audio.has_value()) {
audio_renditions = GetOrCreateRenditionGroup({}, audio_rendition_groups,
inf_tag->audio->Str());
} else {
audio_renditions =
GetOrCreateRenditionGroup({}, audio_rendition_groups, std::nullopt);
}
scoped_refptr<RenditionGroup> video_renditions =
base::MakeRefCounted<RenditionGroup>(
base::PassKey<MultivariantPlaylist>{}, "DEFAULT");
RenditionGroup::RenditionTrack implicit_rendition =
video_renditions->MakeImplicitRendition(
{}, MediaType::kVideo, variant_uri,
rendition_id_generator.GenerateNextId());
variants.emplace_back(
std::move(variant_uri), inf_tag->bandwidth, inf_tag->average_bandwidth,
inf_tag->score, std::move(inf_tag->codecs), inf_tag->resolution,
inf_tag->frame_rate, std::move(audio_renditions),
std::move(video_renditions), std::move(implicit_rendition));
inf_tag.reset();
}
if (inf_tag.has_value()) {
return ParseStatusCode::kXStreamInfTagNotFollowedByUri;
}
if (!common_state.CheckVersion(version)) {
return ParseStatusCode::kPlaylistHasVersionMismatch;
}
for (const auto& group : audio_rendition_groups) {
if (group.first.has_value() != group.second->HasTracks()) {
return ParseStatusCode::kRenditionGroupDoesNotExist;
}
}
auto variant_format = VariantStream::OptimalFormatForCollection(variants);
int variant_index = 0;
for (VariantStream& variant : variants) {
std::string name = variant.Format(variant_format, ++variant_index);
variant.UpdateImplicitRenditionMediaTrackName(name);
}
return base::MakeRefCounted<MultivariantPlaylist>(
base::PassKey<MultivariantPlaylist>(), std::move(uri), version,
common_state.independent_segments_tag.has_value(), std::move(variants),
std::move(common_state.variable_dict));
}
MultivariantPlaylist::MultivariantPlaylist(
base::PassKey<MultivariantPlaylist>,
GURL uri,
types::DecimalInteger version,
bool independent_segments,
std::vector<VariantStream> variants,
VariableDictionary variable_dictionary)
: Playlist(std::move(uri), version, independent_segments),
variants_(std::move(variants)),
variable_dictionary_(std::move(variable_dictionary)) {}
}