blob: af1f4937d5999adb8cbb4ef44e2073214c27f923 [file] [log] [blame]
// Copyright 2022 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/formats/hls/playlist_common.h"
#include "base/notreached.h"
#include "media/formats/hls/playlist.h"
#include "media/formats/hls/types.h"
namespace media::hls {
bool CommonParserState::CheckVersion(
types::DecimalInteger expected_version) const {
if (version_tag.has_value()) {
return expected_version == version_tag->version;
} else {
return expected_version == Playlist::kDefaultVersion;
}
}
ParseStatus::Or<M3uTag> CheckM3uTag(SourceLineIterator* src_iter) {
auto item_result = GetNextLineItem(src_iter);
if (!item_result.has_value()) {
return ParseStatus(ParseStatusCode::kPlaylistMissingM3uTag)
.AddCause(std::move(item_result).error());
}
auto item = std::move(item_result).value();
if (auto* tag_item = absl::get_if<TagItem>(&item)) {
// The #EXTM3U tag must be the first line in the playlist
if (tag_item->GetName() != ToTagName(CommonTagName::kM3u) ||
tag_item->GetLineNumber() != 1) {
return ParseStatusCode::kPlaylistMissingM3uTag;
}
// Make sure the M3U tag parses correctly
auto result = M3uTag::Parse(*tag_item);
if (!result.has_value()) {
return ParseStatus(ParseStatusCode::kPlaylistMissingM3uTag)
.AddCause(std::move(result).error());
}
return result;
}
return ParseStatusCode::kPlaylistMissingM3uTag;
}
void HandleUnknownTag(TagItem /*tag*/) {
// Unknown tags are ignored for forward-compatibility purposes.
// TODO(crbug.com/1266991): Should record a metric to discover common
// unrecognized tags.
}
absl::optional<ParseStatus> ParseCommonTag(TagItem tag,
CommonParserState* state) {
DCHECK(tag.GetName() && GetTagKind(*tag.GetName()) == TagKind::kCommonTag);
switch (static_cast<CommonTagName>(*tag.GetName())) {
case CommonTagName::kM3u: {
// This tag is meant to occur on the first line (which we've already
// checked), however the spec does not explicitly regard this as an
// error if it appears elsewhere as well.
DCHECK(tag.GetLineNumber() != 1);
break;
}
case CommonTagName::kXDefine: {
auto tag_result = XDefineTag::Parse(tag);
if (!tag_result.has_value()) {
return std::move(tag_result).error();
}
auto tag_value = std::move(tag_result).value();
// Imported variables have a null `value` member. If that's the case, look
// up the value in the parent playlist dictionary.
if (!tag_value.value) {
if (!state->parent_variable_dict) {
return ParseStatusCode::kImportedVariableInParentlessPlaylist;
}
auto value = state->parent_variable_dict->Find(tag_value.name);
if (!value) {
return ParseStatusCode::kImportedVariableUndefined;
}
tag_value.value = *value;
}
// Insert the definition, ensuring it has not been defined twice
if (!state->variable_dict.Insert(tag_value.name,
std::string{*tag_value.value})) {
return ParseStatusCode::kVariableDefinedMultipleTimes;
}
break;
}
case CommonTagName::kXIndependentSegments: {
return ParseUniqueTag(tag, state->independent_segments_tag);
}
case CommonTagName::kXStart: {
// TODO(crbug.com/1266991): Implement the EXT-X-START tag.
break;
}
case CommonTagName::kXVersion: {
auto error = ParseUniqueTag(tag, state->version_tag);
if (error.has_value()) {
return error;
}
break;
}
}
return absl::nullopt;
}
ParseStatus::Or<GURL> ParseUri(
UriItem item,
const GURL& playlist_uri,
const CommonParserState& state,
VariableDictionary::SubstitutionBuffer& sub_buffer) {
// Variables may appear in URIs, check for any occurrences and resolve them.
auto uri_str_result = state.variable_dict.Resolve(item.content, sub_buffer);
if (!uri_str_result.has_value()) {
return std::move(uri_str_result).error();
}
// URIs may be relative to the playlist URI, resolve it against that.
auto resolved_uri =
playlist_uri.Resolve(std::move(uri_str_result).value().Str());
if (!resolved_uri.is_valid()) {
return ParseStatusCode::kInvalidUri;
}
return resolved_uri;
}
} // namespace media::hls