summaryrefslogtreecommitdiff
path: root/chromium/media/formats/hls/playlist_common.cc
blob: ec60c8f7ae2c58d625f53342f72494192fd14afe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 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_error()) {
    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_error()) {
      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_error()) {
        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_error()) {
    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